mirror of https://github.com/arendst/Tasmota.git
Support for IPv6 DNS records (AAAA) and IPv6 ``Ping`` for ESP32 and ESP8266 (#17417)
This commit is contained in:
parent
ebf87bdfc8
commit
9abe7b1af9
|
@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
## [12.3.1.1] 20221216
|
||||
### Added
|
||||
- Support for IPv6 DNS records (AAAA) and IPv6 ``Ping`` for ESP32 and ESP8266
|
||||
|
||||
### Breaking Changed
|
||||
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
name=DnsClient
|
||||
version=1.0
|
||||
author=MCQN Ltd, Theo Arends
|
||||
maintainer=Theo
|
||||
sentence=Dns client allowing timeout selection.
|
||||
paragraph=This class uses WifiUdp.
|
||||
architectures=esp8266,esp32
|
|
@ -1,334 +0,0 @@
|
|||
/*
|
||||
DnsClient.cpp - DNS client for Arduino
|
||||
|
||||
SPDX-FileCopyrightText: 2009-2010 MCQN Ltd. and Theo Arends
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
// Arduino DNS client for WizNet5100-based Ethernet shield
|
||||
// (c) Copyright 2009-2010 MCQN Ltd.
|
||||
// Released under Apache License, version 2.0
|
||||
|
||||
#include "DnsClient.h"
|
||||
|
||||
// Various flags and header field values for a DNS message
|
||||
#define UDP_HEADER_SIZE 8
|
||||
#define DNS_HEADER_SIZE 12
|
||||
#define TTL_SIZE 4
|
||||
#define QUERY_FLAG (0)
|
||||
#define RESPONSE_FLAG (1<<15)
|
||||
#define QUERY_RESPONSE_MASK (1<<15)
|
||||
#define OPCODE_STANDARD_QUERY (0)
|
||||
#define OPCODE_INVERSE_QUERY (1<<11)
|
||||
#define OPCODE_STATUS_REQUEST (2<<11)
|
||||
#define OPCODE_MASK (15<<11)
|
||||
#define AUTHORITATIVE_FLAG (1<<10)
|
||||
#define TRUNCATION_FLAG (1<<9)
|
||||
#define RECURSION_DESIRED_FLAG (1<<8)
|
||||
#define RECURSION_AVAILABLE_FLAG (1<<7)
|
||||
#define RESP_NO_ERROR (0)
|
||||
#define RESP_FORMAT_ERROR (1)
|
||||
#define RESP_SERVER_FAILURE (2)
|
||||
#define RESP_NAME_ERROR (3)
|
||||
#define RESP_NOT_IMPLEMENTED (4)
|
||||
#define RESP_REFUSED (5)
|
||||
#define RESP_MASK (15)
|
||||
#define TYPE_A (0x0001)
|
||||
#define CLASS_IN (0x0001)
|
||||
#define LABEL_COMPRESSION_MASK (0xC0)
|
||||
// Port number that DNS servers listen on
|
||||
#define DNS_PORT 53
|
||||
|
||||
// Possible return codes from ProcessResponse
|
||||
#define SUCCESS 1
|
||||
#define TIMED_OUT -1
|
||||
#define INVALID_SERVER -2
|
||||
#define TRUNCATED -3
|
||||
#define INVALID_RESPONSE -4
|
||||
|
||||
#ifndef htons
|
||||
#define htons(x) ( ((x)<< 8 & 0xFF00) | ((x)>> 8 & 0x00FF) )
|
||||
#endif
|
||||
|
||||
void DNSClient::begin(const IPAddress& aDNSServer) {
|
||||
iDNSServer = aDNSServer;
|
||||
iRequestId = 0;
|
||||
}
|
||||
|
||||
void DNSClient::setTimeout(uint32_t aTimeout) {
|
||||
iTimeout = aTimeout;
|
||||
}
|
||||
|
||||
int DNSClient::getHostByName(const char* aHostname, IPAddress& aResult) {
|
||||
// See if it's a numeric IP address
|
||||
if (aResult.fromString(aHostname)) {
|
||||
// It is, our work here is done
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
// Check we've got a valid DNS server to use
|
||||
if ((0xFFFFFFFF == (uint32_t)iDNSServer) || (0 == (uint32_t)iDNSServer)) {
|
||||
return INVALID_SERVER;
|
||||
}
|
||||
|
||||
int ret = 0;
|
||||
// Find a socket to use
|
||||
if (iUdp.begin(1024+(millis() & 0xF)) == 1) {
|
||||
// Try up to three times
|
||||
int retries = 0;
|
||||
// while ((retries < 3) && (ret <= 0)) {
|
||||
// Send DNS request
|
||||
ret = iUdp.beginPacket(iDNSServer, DNS_PORT);
|
||||
if (ret != 0) {
|
||||
// Now output the request data
|
||||
ret = BuildRequest(aHostname);
|
||||
if (ret != 0) {
|
||||
// And finally send the request
|
||||
ret = iUdp.endPacket();
|
||||
if (ret != 0) {
|
||||
// Now wait for a response
|
||||
int wait_retries = 0;
|
||||
ret = TIMED_OUT;
|
||||
while ((wait_retries < 3) && (ret == TIMED_OUT)) {
|
||||
ret = ProcessResponse(iTimeout, aResult);
|
||||
wait_retries++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
retries++;
|
||||
// }
|
||||
// We're done with the socket now
|
||||
iUdp.stop();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int DNSClient::BuildRequest(const char* aName) {
|
||||
// Build header
|
||||
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | ID |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// |QR| Opcode |AA|TC|RD|RA| Z | RCODE |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | QDCOUNT |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | ANCOUNT |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | NSCOUNT |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | ARCOUNT |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// As we only support one request at a time at present, we can simplify
|
||||
// some of this header
|
||||
iRequestId = millis(); // generate a random ID
|
||||
uint16_t twoByteBuffer;
|
||||
|
||||
// FIXME We should also check that there's enough space available to write to, rather
|
||||
// FIXME than assume there's enough space (as the code does at present)
|
||||
iUdp.write((uint8_t*)&iRequestId, sizeof(iRequestId));
|
||||
|
||||
twoByteBuffer = htons(QUERY_FLAG | OPCODE_STANDARD_QUERY | RECURSION_DESIRED_FLAG);
|
||||
iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer));
|
||||
|
||||
twoByteBuffer = htons(1); // One question record
|
||||
iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer));
|
||||
|
||||
twoByteBuffer = 0; // Zero answer records
|
||||
iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer));
|
||||
|
||||
iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer));
|
||||
// and zero additional records
|
||||
iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer));
|
||||
|
||||
// Build question
|
||||
const char* start =aName;
|
||||
const char* end =start;
|
||||
uint8_t len;
|
||||
// Run through the name being requested
|
||||
while (*end) {
|
||||
// Find out how long this section of the name is
|
||||
end = start;
|
||||
while (*end && (*end != '.') ) {
|
||||
end++;
|
||||
}
|
||||
|
||||
if (end-start > 0) {
|
||||
// Write out the size of this section
|
||||
len = end-start;
|
||||
iUdp.write(&len, sizeof(len));
|
||||
// And then write out the section
|
||||
iUdp.write((uint8_t*)start, end-start);
|
||||
}
|
||||
start = end+1;
|
||||
}
|
||||
|
||||
// We've got to the end of the question name, so terminate it with a zero-length section
|
||||
len = 0;
|
||||
iUdp.write(&len, sizeof(len));
|
||||
// Finally the type and class of question
|
||||
twoByteBuffer = htons(TYPE_A);
|
||||
iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer));
|
||||
|
||||
twoByteBuffer = htons(CLASS_IN); // Internet class of question
|
||||
iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer));
|
||||
// Success! Everything buffered okay
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
int DNSClient::ProcessResponse(uint32_t aTimeout, IPAddress& aAddress) {
|
||||
uint32_t startTime = millis();
|
||||
|
||||
// Wait for a response packet
|
||||
while(iUdp.parsePacket() <= 0) {
|
||||
if ((millis() - startTime) > aTimeout) {
|
||||
return TIMED_OUT;
|
||||
}
|
||||
delay(20);
|
||||
}
|
||||
|
||||
// We've had a reply!
|
||||
// Read the UDP header
|
||||
uint8_t header[DNS_HEADER_SIZE]; // Enough space to reuse for the DNS header
|
||||
// Check that it's a response from the right server and the right port
|
||||
if ( (iDNSServer != iUdp.remoteIP()) || (iUdp.remotePort() != DNS_PORT) ) {
|
||||
// It's not from who we expected
|
||||
return INVALID_SERVER;
|
||||
}
|
||||
|
||||
// Read through the rest of the response
|
||||
if (iUdp.available() < DNS_HEADER_SIZE) {
|
||||
return TRUNCATED;
|
||||
}
|
||||
iUdp.read(header, DNS_HEADER_SIZE);
|
||||
|
||||
uint16_t staging; // Staging used to avoid type-punning warnings
|
||||
memcpy(&staging, &header[2], sizeof(uint16_t));
|
||||
uint16_t header_flags = htons(staging);
|
||||
memcpy(&staging, &header[0], sizeof(uint16_t));
|
||||
// Check that it's a response to this request
|
||||
if ( (iRequestId != staging) || ((header_flags & QUERY_RESPONSE_MASK) != (uint16_t)RESPONSE_FLAG) ) {
|
||||
// Mark the entire packet as read
|
||||
iUdp.flush();
|
||||
return INVALID_RESPONSE;
|
||||
}
|
||||
// Check for any errors in the response (or in our request)
|
||||
// although we don't do anything to get round these
|
||||
if ( (header_flags & TRUNCATION_FLAG) || (header_flags & RESP_MASK) ) {
|
||||
// Mark the entire packet as read
|
||||
iUdp.flush();
|
||||
return -5; // INVALID_RESPONSE;
|
||||
}
|
||||
|
||||
// And make sure we've got (at least) one answer
|
||||
memcpy(&staging, &header[6], sizeof(uint16_t));
|
||||
uint16_t answerCount = htons(staging);
|
||||
if (answerCount == 0 ) {
|
||||
// Mark the entire packet as read
|
||||
iUdp.flush();
|
||||
return -6; // INVALID_RESPONSE;
|
||||
}
|
||||
|
||||
// Skip over any questions
|
||||
memcpy(&staging, &header[4], sizeof(uint16_t));
|
||||
for (uint32_t i = 0; i < htons(staging); i++) {
|
||||
// Skip over the name
|
||||
uint8_t len;
|
||||
do {
|
||||
iUdp.read(&len, sizeof(len));
|
||||
if (len > 0) {
|
||||
// Don't need to actually read the data out for the string, just
|
||||
// advance ptr to beyond it
|
||||
while(len--) {
|
||||
iUdp.read(); // we don't care about the returned byte
|
||||
}
|
||||
}
|
||||
} while (len != 0);
|
||||
|
||||
// Now jump over the type and class
|
||||
for (uint32_t i = 0; i < 4; i++) {
|
||||
iUdp.read(); // we don't care about the returned byte
|
||||
}
|
||||
}
|
||||
|
||||
// Now we're up to the bit we're interested in, the answer
|
||||
// There might be more than one answer (although we'll just use the first
|
||||
// type A answer) and some authority and additional resource records but
|
||||
// we're going to ignore all of them.
|
||||
|
||||
for (uint32_t i = 0; i < answerCount; i++) {
|
||||
// Skip the name
|
||||
uint8_t len;
|
||||
do {
|
||||
iUdp.read(&len, sizeof(len));
|
||||
if ((len & LABEL_COMPRESSION_MASK) == 0) {
|
||||
// It's just a normal label
|
||||
if (len > 0) {
|
||||
// And it's got a length
|
||||
// Don't need to actually read the data out for the string,
|
||||
// just advance ptr to beyond it
|
||||
while(len--) {
|
||||
iUdp.read(); // we don't care about the returned byte
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This is a pointer to a somewhere else in the message for the
|
||||
// rest of the name. We don't care about the name, and RFC1035
|
||||
// says that a name is either a sequence of labels ended with a
|
||||
// 0 length octet or a pointer or a sequence of labels ending in
|
||||
// a pointer. Either way, when we get here we're at the end of
|
||||
// the name
|
||||
// Skip over the pointer
|
||||
iUdp.read(); // we don't care about the returned byte
|
||||
// And set len so that we drop out of the name loop
|
||||
len = 0;
|
||||
}
|
||||
} while (len != 0);
|
||||
|
||||
// Check the type and class
|
||||
uint16_t answerType;
|
||||
uint16_t answerClass;
|
||||
iUdp.read((uint8_t*)&answerType, sizeof(answerType));
|
||||
iUdp.read((uint8_t*)&answerClass, sizeof(answerClass));
|
||||
|
||||
// Ignore the Time-To-Live as we don't do any caching
|
||||
for (uint32_t i = 0; i < TTL_SIZE; i++) {
|
||||
iUdp.read(); // We don't care about the returned byte
|
||||
}
|
||||
|
||||
// And read out the length of this answer
|
||||
// Don't need header_flags anymore, so we can reuse it here
|
||||
iUdp.read((uint8_t*)&header_flags, sizeof(header_flags));
|
||||
|
||||
if ( (htons(answerType) == TYPE_A) && (htons(answerClass) == CLASS_IN) ) {
|
||||
if (htons(header_flags) != 4) {
|
||||
// It's a weird size
|
||||
// Mark the entire packet as read
|
||||
iUdp.flush();
|
||||
return -9; // INVALID_RESPONSE;
|
||||
}
|
||||
iUdp.read(aAddress.raw_address(), 4);
|
||||
// uint32_t address;
|
||||
// iUdp.read((uint8_t*)&address, sizeof(address));
|
||||
// aAddress = (IPAddress)address;
|
||||
|
||||
// Check we've got a valid address
|
||||
if ((0xFFFFFFFF != (uint32_t)aAddress) && (0 != (uint32_t)aAddress)) {
|
||||
return SUCCESS;
|
||||
}
|
||||
} else {
|
||||
// This isn't an answer type we're after, move onto the next one
|
||||
for (uint32_t i = 0; i < htons(header_flags); i++) {
|
||||
iUdp.read(); // we don't care about the returned byte
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mark the entire packet as read
|
||||
iUdp.flush();
|
||||
|
||||
// If we get here then we haven't found an answer
|
||||
return -10; // INVALID_RESPONSE;
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
/*
|
||||
DnsClient.h - DNS client for Arduino
|
||||
|
||||
SPDX-FileCopyrightText: 2009-2010 MCQN Ltd. and Theo Arends
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
// Arduino DNS client for WizNet5100-based Ethernet shield
|
||||
// (c) Copyright 2009-2010 MCQN Ltd.
|
||||
// Released under Apache License, version 2.0
|
||||
|
||||
#ifndef DNSClient_h
|
||||
#define DNSClient_h
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <IPAddress.h>
|
||||
#include <WiFiUdp.h>
|
||||
|
||||
class DNSClient {
|
||||
public:
|
||||
void begin(const IPAddress& aDNSServer);
|
||||
void setTimeout(uint32_t aTimeout = 1000);
|
||||
|
||||
/* Resolve the given hostname to an IP address.
|
||||
@param aHostname Name to be resolved
|
||||
@param aResult IPAddress structure to store the returned IP address
|
||||
@result 1 if aIPAddrString was successfully converted to an IP address, else error code
|
||||
*/
|
||||
int getHostByName(const char* aHostname, IPAddress& aResult);
|
||||
|
||||
protected:
|
||||
int BuildRequest(const char* aName);
|
||||
int ProcessResponse(uint32_t aTimeout, IPAddress& aAddress);
|
||||
|
||||
IPAddress iDNSServer;
|
||||
uint16_t iRequestId;
|
||||
uint16_t iTimeout = 1000;
|
||||
WiFiUDP iUdp;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -28,6 +28,55 @@
|
|||
#undef WiFi
|
||||
#endif
|
||||
|
||||
#include "tasmota_options.h"
|
||||
#include "lwip/dns.h"
|
||||
|
||||
wl_status_t WiFiClass32::begin(const char* wpa2_ssid, wpa2_auth_method_t method, const char* wpa2_identity, const char* wpa2_username, const char *wpa2_password, const char* ca_pem, const char* client_crt, const char* client_key, int32_t channel, const uint8_t* bssid, bool connect) {
|
||||
saveDNS();
|
||||
wl_status_t ret = WiFiClass::begin(wpa2_ssid, method, wpa2_identity, wpa2_username, wpa2_password, ca_pem, client_crt, client_key, channel, bssid, connect);
|
||||
restoreDNS();
|
||||
return ret;
|
||||
}
|
||||
|
||||
wl_status_t WiFiClass32::begin(const char* ssid, const char *passphrase, int32_t channel, const uint8_t* bssid, bool connect) {
|
||||
saveDNS();
|
||||
wl_status_t ret = WiFiClass::begin(ssid, passphrase, channel, bssid, connect);
|
||||
restoreDNS();
|
||||
return ret;
|
||||
}
|
||||
|
||||
wl_status_t WiFiClass32::begin(char* ssid, char *passphrase, int32_t channel, const uint8_t* bssid, bool connect) {
|
||||
saveDNS();
|
||||
wl_status_t ret = WiFiClass::begin(ssid, passphrase, channel, bssid, connect);
|
||||
restoreDNS();
|
||||
return ret;
|
||||
}
|
||||
wl_status_t WiFiClass32::begin() {
|
||||
saveDNS();
|
||||
wl_status_t ret = WiFiClass::begin();
|
||||
restoreDNS();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void WiFiClass32::saveDNS(void) {
|
||||
// save the DNS servers
|
||||
for (uint32_t i=0; i<DNS_MAX_SERVERS; i++) {
|
||||
const ip_addr_t * ip = dns_getserver(i);
|
||||
if (!ip_addr_isany(ip)) {
|
||||
dns_save[i] = *ip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WiFiClass32::restoreDNS(void) {
|
||||
// restore DNS server if it was removed
|
||||
for (uint32_t i=0; i<DNS_MAX_SERVERS; i++) {
|
||||
if (ip_addr_isany(dns_getserver(i))) {
|
||||
dns_setserver(i, &dns_save[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WiFiClass32::setSleepMode(int iSleepMode) {
|
||||
// WIFI_LIGHT_SLEEP and WIFI_MODEM_SLEEP
|
||||
WiFi.setSleep(iSleepMode != WIFI_NONE_SLEEP);
|
||||
|
@ -91,14 +140,89 @@ bool WiFiClass32::getNetworkInfo(uint8_t i, String &ssid, uint8_t &encType, int3
|
|||
return WiFi.getNetworkInfo(i, ssid, encType, rssi, bssid, channel);
|
||||
}
|
||||
|
||||
// from https://github.com/espressif/arduino-esp32/pull/7520
|
||||
static const int WIFI_WANT_IP6_BIT_ALT = BIT15;
|
||||
bool WiFiClass32::IPv6(bool state) {
|
||||
if (state)
|
||||
WiFiGenericClass::setStatusBits(WIFI_WANT_IP6_BIT_ALT);
|
||||
else
|
||||
WiFiGenericClass::clearStatusBits(WIFI_WANT_IP6_BIT_ALT);
|
||||
return true;
|
||||
//
|
||||
// Manage dns callbacks from lwip DNS resolver.
|
||||
// We need a trick here, because the callback may be called after we timed-out and
|
||||
// launched a new request. We need to discard outdated responses.
|
||||
//
|
||||
// We use a static ip_addr_t (anyways we don't support multi-threading)
|
||||
// and use a counter so the callback can check if it's responding to the current
|
||||
// request or to an old one (hence discard)
|
||||
//
|
||||
// It's not an issue to have old requests in flight. LWIP has a default limit of 4
|
||||
// DNS requests in flight.
|
||||
// If the buffer for in-flight requests is full, LWIP removes the oldest from the list.
|
||||
// (it does not block new DNS resolutions)
|
||||
static ip_addr_t dns_ipaddr;
|
||||
static volatile uint32_t ip_addr_counter = 0; // counter for requests
|
||||
extern int32_t WifiDNSGetTimeout(void);
|
||||
extern bool WifiDNSGetIPv6Priority(void);
|
||||
|
||||
static void wifi32_dns_found_callback(const char *name, const ip_addr_t *ipaddr, void *callback_arg)
|
||||
{
|
||||
// Serial.printf("DNS: cb name=%s ipaddr=%s arg=%i counter=%i\n", name ? name : "<null>", IPAddress(ipaddr).toString().c_str(), (int) callback_arg, ip_addr_counter);
|
||||
uint32_t cb_counter = (uint32_t) callback_arg;
|
||||
if (cb_counter != ip_addr_counter) { return; } // the response is from a previous request, ignore
|
||||
|
||||
if (ipaddr != nullptr) {
|
||||
dns_ipaddr = *ipaddr;
|
||||
} else {
|
||||
dns_ipaddr = *IP4_ADDR_ANY; // set to IPv4 0.0.0.0
|
||||
}
|
||||
WiFiClass32::dnsDone();
|
||||
// AddLog(LOG_LEVEL_DEBUG, "WIF: dns_found=%s", ipaddr ? IPAddress(*ipaddr).toString().c_str() : "<null>");
|
||||
}
|
||||
// We need this helper method to access protected methods from WiFiGeneric
|
||||
void WiFiClass32::dnsDone(void) {
|
||||
setStatusBits(WIFI_DNS_DONE_BIT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the given hostname to an IP address.
|
||||
* @param aHostname Name to be resolved
|
||||
* @param aResult IPAddress structure to store the returned IP address
|
||||
* @return 1 if aIPAddrString was successfully converted to an IP address,
|
||||
* else error code
|
||||
*/
|
||||
int WiFiClass32::hostByName(const char* aHostname, IPAddress& aResult, int32_t timer_ms)
|
||||
{
|
||||
ip_addr_t addr;
|
||||
aResult = (uint32_t) 0; // by default set to IPv4 0.0.0.0
|
||||
dns_ipaddr = *IP4_ADDR_ANY; // by default set to IPv4 0.0.0.0
|
||||
|
||||
ip_addr_counter++; // increase counter, from now ignore previous responses
|
||||
clearStatusBits(WIFI_DNS_IDLE_BIT | WIFI_DNS_DONE_BIT);
|
||||
uint8_t v4v6priority = LWIP_DNS_ADDRTYPE_IPV4;
|
||||
#ifdef USE_IPV6
|
||||
v4v6priority = WifiDNSGetIPv6Priority() ? LWIP_DNS_ADDRTYPE_IPV6_IPV4 : LWIP_DNS_ADDRTYPE_IPV4_IPV6;
|
||||
#endif // USE_IPV6
|
||||
err_t err = dns_gethostbyname_addrtype(aHostname, &dns_ipaddr, &wifi32_dns_found_callback, (void*) ip_addr_counter, v4v6priority);
|
||||
// Serial.printf("DNS: dns_gethostbyname_addrtype errg=%i counter=%i\n", err, ip_addr_counter);
|
||||
if(err == ERR_OK && !ip_addr_isany_val(dns_ipaddr)) {
|
||||
#ifdef USE_IPV6
|
||||
aResult = dns_ipaddr;
|
||||
#else // USE_IPV6
|
||||
aResult = ip_addr_get_ip4_u32(&dns_ipaddr);
|
||||
#endif // USE_IPV6
|
||||
} else if(err == ERR_INPROGRESS) {
|
||||
waitStatusBits(WIFI_DNS_DONE_BIT, timer_ms); //real internal timeout in lwip library is 14[s]
|
||||
clearStatusBits(WIFI_DNS_DONE_BIT);
|
||||
}
|
||||
|
||||
if (!ip_addr_isany_val(dns_ipaddr)) {
|
||||
#ifdef USE_IPV6
|
||||
aResult = dns_ipaddr;
|
||||
#else // USE_IPV6
|
||||
aResult = ip_addr_get_ip4_u32(&dns_ipaddr);
|
||||
#endif // USE_IPV6
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int WiFiClass32::hostByName(const char* aHostname, IPAddress& aResult)
|
||||
{
|
||||
return hostByName(aHostname, aResult, WifiDNSGetTimeout());
|
||||
}
|
||||
|
||||
void wifi_station_disconnect() {
|
||||
|
|
|
@ -37,6 +37,11 @@ typedef enum WiFiPhyMode
|
|||
class WiFiClass32 : public WiFiClass
|
||||
{
|
||||
public:
|
||||
wl_status_t begin(const char* wpa2_ssid, wpa2_auth_method_t method, const char* wpa2_identity=NULL, const char* wpa2_username=NULL, const char *wpa2_password=NULL, const char* ca_pem=NULL, const char* client_crt=NULL, const char* client_key=NULL, int32_t channel=0, const uint8_t* bssid=0, bool connect=true);
|
||||
wl_status_t begin(const char* ssid, const char *passphrase = NULL, int32_t channel = 0, const uint8_t* bssid = NULL, bool connect = true);
|
||||
wl_status_t begin(char* ssid, char *passphrase = NULL, int32_t channel = 0, const uint8_t* bssid = NULL, bool connect = true);
|
||||
wl_status_t begin();
|
||||
|
||||
static void hostname(const char* aHostname)
|
||||
{
|
||||
WiFi.setHostname(aHostname);
|
||||
|
@ -51,7 +56,14 @@ public:
|
|||
static void forceSleepWake();
|
||||
static bool getNetworkInfo(uint8_t i, String &ssid, uint8_t &encType, int32_t &rssi, uint8_t* &bssid, int32_t &channel, bool &hidden_scan);
|
||||
|
||||
bool IPv6(bool state); // make sure it always exists even with older Arduino framework
|
||||
static void dnsDone(void); // used by the callback to stop the dns timer
|
||||
int hostByName(const char* aHostname, IPAddress& aResult, int32_t timer_ms);
|
||||
int hostByName(const char* aHostname, IPAddress& aResult);
|
||||
|
||||
void saveDNS(void);
|
||||
void restoreDNS(void);
|
||||
protected:
|
||||
ip_addr_t dns_save[DNS_MAX_SERVERS] = {};
|
||||
};
|
||||
|
||||
void wifi_station_disconnect();
|
||||
|
|
|
@ -182,7 +182,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu
|
|||
uint32_t use_esp32_temperature : 1; // bit 0 (v12.1.1.1) - SetOption146 - (ESP32) Show ESP32 internal temperature sensor
|
||||
uint32_t mqtt_disable_sserialrec : 1; // bit 1 (v12.1.1.2) - SetOption147 - (MQTT) Disable publish SSerialReceived MQTT messages, you must use event trigger rules instead.
|
||||
uint32_t artnet_autorun : 1; // bit 2 (v12.2.0.4) - SetOption148 - (Light) start DMX ArtNet at boot, listen to UDP port as soon as network is up
|
||||
uint32_t spare03 : 1; // bit 3
|
||||
uint32_t dns_ipv6_priority : 1; // bit 3 (v12.2.0.6) - SetOption149 - (Wifi) prefer IPv6 DNS resolution to IPv4 address when available. Requires `#define USE_IPV6`
|
||||
uint32_t spare04 : 1; // bit 4
|
||||
uint32_t spare05 : 1; // bit 5
|
||||
uint32_t spare06 : 1; // bit 6
|
||||
|
|
|
@ -42,7 +42,6 @@
|
|||
// Libraries
|
||||
#include <ESP8266HTTPClient.h> // Ota
|
||||
#include <ESP8266httpUpdate.h> // Ota
|
||||
#include <DnsClient.h> // Any getHostByName
|
||||
#ifdef ESP32
|
||||
#ifdef USE_TLS
|
||||
#include "HTTPUpdateLight.h" // Ota over HTTPS for ESP32
|
||||
|
@ -193,7 +192,6 @@ struct XDRVMAILBOX {
|
|||
char *command;
|
||||
} XdrvMailbox;
|
||||
|
||||
DNSClient DnsClient;
|
||||
WiFiUDP PortUdp; // UDP Syslog and Alexa
|
||||
|
||||
#ifdef ESP32
|
||||
|
@ -634,7 +632,6 @@ void setup(void) {
|
|||
TasmotaGlobal.init_state = INIT_GPIOS;
|
||||
|
||||
SetPowerOnState();
|
||||
DnsClient.setTimeout(Settings->dns_timeout);
|
||||
WifiConnect();
|
||||
|
||||
AddLog(LOG_LEVEL_INFO, PSTR(D_PROJECT " %s - %s " D_VERSION " %s%s-" ARDUINO_CORE_RELEASE "(%s)"),
|
||||
|
|
|
@ -2516,7 +2516,6 @@ void CmndDnsTimeout(void) {
|
|||
// Set timeout between 100 and 20000 mSec
|
||||
if ((XdrvMailbox.payload >= 100) && (XdrvMailbox.payload <= 20000)) {
|
||||
Settings->dns_timeout = XdrvMailbox.payload;
|
||||
DnsClient.setTimeout(Settings->dns_timeout);
|
||||
}
|
||||
ResponseCmndNumber(Settings->dns_timeout);
|
||||
}
|
||||
|
|
|
@ -500,6 +500,7 @@ void CreateLinkLocalIPv6(void)
|
|||
}
|
||||
|
||||
//
|
||||
#include "lwip/dns.h"
|
||||
void WifiDumpAddressesIPv6(void)
|
||||
{
|
||||
for (netif* intf = netif_list; intf != nullptr; intf = intf->next) {
|
||||
|
@ -511,6 +512,8 @@ void WifiDumpAddressesIPv6(void)
|
|||
ip_addr_islinklocal(&intf->ip6_addr[i]) ? "local" : "");
|
||||
}
|
||||
}
|
||||
AddLog(LOG_LEVEL_DEBUG, "WIF: DNS(0): %s", IPAddress(dns_getserver(0)).toString().c_str());
|
||||
AddLog(LOG_LEVEL_DEBUG, "WIF: DNS(1): %s", IPAddress(dns_getserver(1)).toString().c_str());
|
||||
}
|
||||
#endif // USE_IPV6
|
||||
|
||||
|
@ -890,37 +893,56 @@ void wifiKeepAlive(void) {
|
|||
}
|
||||
#endif // ESP8266
|
||||
|
||||
// expose a function to be called by WiFi32
|
||||
int32_t WifiDNSGetTimeout(void) {
|
||||
return Settings->dns_timeout;
|
||||
}
|
||||
// read Settings for DNS IPv6 priority
|
||||
bool WifiDNSGetIPv6Priority(void) {
|
||||
#ifdef USE_IPV6
|
||||
// we prioritize IPv6 only if a global IPv6 address is available, otherwise revert to IPv4 if we have one as well
|
||||
// Any change in logic needs to clear the DNS cache
|
||||
static bool had_v6prio = false;
|
||||
|
||||
const ip_addr_t &local_ip = (ip_addr_t)WiFi.localIP();
|
||||
bool has_v4 = !ip_addr_isany_val(local_ip) && IP_IS_V4_VAL(local_ip);
|
||||
bool has_v6 = WifiGetIPv6().length() != 0;
|
||||
#ifdef USE_ETHERNET
|
||||
const ip_addr_t &local_ip_eth = (ip_addr_t)EthernetLocalIP();
|
||||
has_v4 = has_v4 || (!ip_addr_isany_val(local_ip_eth) && IP_IS_V4_VAL(local_ip_eth));
|
||||
has_v6 = has_v6 || EthernetGetIPv6().length() != 0;
|
||||
#endif
|
||||
|
||||
bool v6prio = Settings->flag6.dns_ipv6_priority;
|
||||
// AddLog(LOG_LEVEL_DEBUG, "WIF: v6 priority was %i, now is %i, has_v4=%i has_v6=%i", had_v6prio, v6prio, has_v4, has_v6);
|
||||
|
||||
if (has_v4 && !has_v6 && v6prio) {
|
||||
v6prio = false; // revert to IPv4 first
|
||||
}
|
||||
|
||||
// any change of state requires a dns cache clear
|
||||
if (had_v6prio != v6prio) {
|
||||
dns_clear_cache();
|
||||
had_v6prio = v6prio;
|
||||
}
|
||||
|
||||
return v6prio;
|
||||
#endif // USE_IPV6
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WifiHostByName(const char* aHostname, IPAddress& aResult) {
|
||||
#ifdef ESP8266
|
||||
if (WiFi.hostByName(aHostname, aResult, Settings->dns_timeout)) {
|
||||
uint32_t dns_start = millis();
|
||||
bool success = WiFi.hostByName(aHostname, aResult, Settings->dns_timeout);
|
||||
uint32_t dns_end = millis();
|
||||
if (success) {
|
||||
// Host name resolved
|
||||
if (0xFFFFFFFF != (uint32_t)aResult) {
|
||||
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_WIFI "DNS resolved '%s' (%s) in %i ms"), aHostname, aResult.toString().c_str(), dns_end - dns_start);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#else
|
||||
// DnsClient can't do one-shot mDNS queries so use WiFi.hostByName() for *.local
|
||||
aResult = (uint32_t) 0x00000000L; // indirectly force to be IPv4, since the client touches the binary format later
|
||||
size_t hostname_len = strlen(aHostname);
|
||||
if (strstr_P(aHostname, PSTR(".local")) == &aHostname[hostname_len] - 6) {
|
||||
if (WiFi.hostByName(aHostname, aResult)) {
|
||||
// Host name resolved
|
||||
if (0xFFFFFFFF != (uint32_t)aResult) {
|
||||
AddLog(LOG_LEVEL_DEBUG, "WIF: Resolving '%s' (%s)", aHostname, aResult.toString().c_str());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Use this instead of WiFi.hostByName or connect(host_name,.. to block less if DNS server is not found
|
||||
uint32_t dns_address = (!TasmotaGlobal.global_state.eth_down) ? Settings->eth_ipv4_address[3] : Settings->ipv4_address[3];
|
||||
DnsClient.begin((IPAddress)dns_address);
|
||||
if (1 == DnsClient.getHostByName(aHostname, aResult)) {
|
||||
AddLog(LOG_LEVEL_DEBUG, "WIF: Resolving '%s' (%s)", aHostname, aResult.toString().c_str());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
AddLog(LOG_LEVEL_DEBUG, PSTR("DNS: Unable to resolve '%s'"), aHostname);
|
||||
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_WIFI "DNS failed for %s after %i ms"), aHostname, dns_end - dns_start);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1091,6 +1113,7 @@ void WifiEvents(arduino_event_t *event) {
|
|||
AddLog(LOG_LEVEL_DEBUG, PSTR("%s: IPv6 %s %s"),
|
||||
event->event_id == ARDUINO_EVENT_ETH_GOT_IP6 ? "ETH" : "WIF",
|
||||
addr.isLocal() ? PSTR("Local") : PSTR("Global"), addr.toString().c_str());
|
||||
WiFi.saveDNS(); // internal calls to reconnect can zero the DNS servers, save DNS for future use
|
||||
}
|
||||
break;
|
||||
#endif // USE_IPV6
|
||||
|
@ -1104,7 +1127,7 @@ void WifiEvents(arduino_event_t *event) {
|
|||
event->event_info.got_ip.ip_info.ip.addr,
|
||||
event->event_info.got_ip.ip_info.netmask.addr,
|
||||
event->event_info.got_ip.ip_info.gw.addr);
|
||||
|
||||
WiFi.saveDNS(); // internal calls to reconnect can zero the DNS servers, save DNS for future use
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -1114,6 +1137,7 @@ void WifiEvents(arduino_event_t *event) {
|
|||
break;
|
||||
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
||||
case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE:
|
||||
WiFi.restoreDNS(); // internal calls to reconnect can zero the DNS servers, restore the previous values
|
||||
Wifi.ipv6_local_link_called = false;
|
||||
break;
|
||||
|
||||
|
|
|
@ -64,6 +64,9 @@ extern "C" {
|
|||
// globals
|
||||
Ping_t *ping_head = nullptr; // head of the Linked List for ping objects
|
||||
struct raw_pcb *t_ping_pcb = nullptr; // registered with first ping, deregistered after last ping, the same pcb is used for all packets
|
||||
#ifdef USE_IPV6
|
||||
struct raw_pcb *t_ping6_pcb = nullptr; // IPv6 version, registered with first ping, deregistered after last ping, the same pcb is used for all packets
|
||||
#endif // USE_IPV6
|
||||
|
||||
// ================================================================================
|
||||
// Find the Ping object indexed by IP address
|
||||
|
@ -75,7 +78,6 @@ extern "C" {
|
|||
Ping_t *ping = ping_head;
|
||||
while (ping != nullptr) {
|
||||
if (ip_addr_cmp(&ping->ip, ip)) {
|
||||
// if (ping->ip == ip) {
|
||||
return ping;
|
||||
}
|
||||
ping = ping->next;
|
||||
|
@ -119,6 +121,31 @@ extern "C" {
|
|||
|
||||
iecho->chksum = inet_chksum(iecho, len);
|
||||
}
|
||||
|
||||
#ifdef USE_IPV6
|
||||
// Prepare a echo ICMP6 request
|
||||
//
|
||||
void t_ping_prepare_echo6(struct icmp6_echo_hdr *iecho6, uint16_t len, Ping_t *ping) {
|
||||
// TODO
|
||||
size_t data_len = len - sizeof(struct icmp6_echo_hdr);
|
||||
|
||||
iecho6->type = ICMP6_TYPE_EREQ;
|
||||
iecho6->code = 0;
|
||||
iecho6->chksum = 0;
|
||||
iecho6->id = Ping_ID;
|
||||
ping->seq_num++;
|
||||
if (ping->seq_num == 0x7fff) { ping->seq_num = 0; }
|
||||
|
||||
iecho6->seqno = htons(ping->seq_num);
|
||||
|
||||
/* fill the additional data buffer with some data */
|
||||
for (uint32_t i = 0; i < data_len; i++) {
|
||||
((char*)iecho6)[sizeof(struct icmp6_echo_hdr) + i] = (char)i;
|
||||
}
|
||||
// checksum is calculated by lwip
|
||||
}
|
||||
#endif // USE_IPV6
|
||||
|
||||
//
|
||||
// send the ICMP packet
|
||||
//
|
||||
|
@ -130,11 +157,31 @@ extern "C" {
|
|||
p = pbuf_alloc(PBUF_IP, ping_size, PBUF_RAM);
|
||||
if (!p) { return; }
|
||||
if ((p->len == p->tot_len) && (p->next == nullptr)) {
|
||||
struct icmp_echo_hdr *iecho;
|
||||
|
||||
iecho = (struct icmp_echo_hdr *) p->payload;
|
||||
t_ping_prepare_echo(iecho, ping_size, ping);
|
||||
raw_sendto(raw, p, &ping->ip);
|
||||
#ifdef USE_IPV6
|
||||
// different format for IPv4 and IPv6 packets
|
||||
if (IP_IS_V6_VAL(ping->ip)) {
|
||||
// IPv6
|
||||
struct icmp6_echo_hdr *iecho6;
|
||||
iecho6 = (struct icmp6_echo_hdr *) p->payload;
|
||||
t_ping_prepare_echo6(iecho6, ping_size, ping);
|
||||
// set parameters for checksum handling
|
||||
t_ping6_pcb->chksum_reqd = 1;
|
||||
t_ping6_pcb->chksum_offset = offsetof(icmp6_echo_hdr, chksum);
|
||||
|
||||
// AddLog(LOG_LEVEL_DEBUG, "PNG: sending ICMP6(%i-%i)=%*_H", p->len, ping_size, p->len, p->payload);
|
||||
raw_sendto(t_ping6_pcb, p, &ping->ip);
|
||||
// AddLog(LOG_LEVEL_DEBUG, "PNG: sending ICMP6(%i-%i)=%*_H", p->len, ping_size, p->len, p->payload);
|
||||
} else
|
||||
#endif // USE_IPV6
|
||||
{
|
||||
// IPv4
|
||||
struct icmp_echo_hdr *iecho;
|
||||
iecho = (struct icmp_echo_hdr *) p->payload;
|
||||
t_ping_prepare_echo(iecho, ping_size, ping);
|
||||
raw_sendto(t_ping_pcb, p, &ping->ip);
|
||||
// AddLog(LOG_LEVEL_DEBUG, "PNG: sending ICMP4(%i-%i)=%*_H", p->len, ping_size, p->len, p->payload);
|
||||
}
|
||||
}
|
||||
pbuf_free(p);
|
||||
}
|
||||
|
@ -148,7 +195,7 @@ extern "C" {
|
|||
if (ping->to_send_count > 0) {
|
||||
ping->to_send_count--;
|
||||
// have we sent all packets?
|
||||
t_ping_send(t_ping_pcb, ping);
|
||||
t_ping_send(t_ping_pcb, ping); // ICMP can also send ICMP6
|
||||
|
||||
sys_timeout(Ping_timeout_ms, t_ping_timeout, ping);
|
||||
sys_timeout(Ping_coarse, t_ping_coarse_tmr, ping);
|
||||
|
@ -165,17 +212,31 @@ extern "C" {
|
|||
// Reveived packet
|
||||
//
|
||||
static uint8_t ICACHE_FLASH_ATTR t_ping_recv(void *arg, struct raw_pcb *pcb, struct pbuf *p, const ip_addr_t *addr) {
|
||||
// AddLog(LOG_LEVEL_DEBUG, "PNG: from %s pub(%i)=%*_H", IPAddress(*addr).toString().c_str(), p->len, p->len, p->payload);
|
||||
Ping_t *ping = t_ping_find(addr);
|
||||
|
||||
if (nullptr == ping) { // unknown source address
|
||||
return 0; // don't eat the packet and ignore it
|
||||
}
|
||||
|
||||
if (pbuf_header( p, -PBUF_TRANSPORT_HLEN)==0) {
|
||||
size_t pbuf_header_len = PBUF_TRANSPORT_HLEN;
|
||||
bool ipv6 = false;
|
||||
#ifdef USE_IPV6
|
||||
if (pcb == t_ping6_pcb) {
|
||||
pbuf_header_len = PBUF_IP_HLEN;
|
||||
ipv6 = true;
|
||||
}
|
||||
#endif // USE_IPV6
|
||||
if (pbuf_header(p, -pbuf_header_len)==0) {
|
||||
// AddLog(LOG_LEVEL_DEBUG, "PNG: received(%i)=%*_H", p->len, p->len, p->payload);
|
||||
struct icmp_echo_hdr *iecho;
|
||||
iecho = (struct icmp_echo_hdr *)p->payload;
|
||||
|
||||
if ((iecho->id == Ping_ID) && (iecho->seqno == htons(ping->seq_num)) && iecho->type == ICMP_ER) {
|
||||
uint8_t icmp_resp_type = ICMP_ER;
|
||||
#ifdef USE_IPV6
|
||||
icmp_resp_type = (ipv6 ? ICMP6_TYPE_EREP : ICMP_ER);
|
||||
#endif // USE_IPV6
|
||||
if ((iecho->id == Ping_ID) && (iecho->seqno == htons(ping->seq_num)) && iecho->type == icmp_resp_type) {
|
||||
|
||||
if (iecho->seqno != ping->seqno){ // debounce already received packet
|
||||
/* do some ping result processing */
|
||||
|
@ -212,8 +273,16 @@ extern "C" {
|
|||
t_ping_pcb = raw_new(IP_PROTO_ICMP);
|
||||
|
||||
raw_recv(t_ping_pcb, t_ping_recv, nullptr); // we cannot register data structure here as we can only register one
|
||||
raw_bind(t_ping_pcb, IP_ADDR_ANY);
|
||||
raw_bind(t_ping_pcb, IP4_ADDR_ANY);
|
||||
}
|
||||
#ifdef USE_IPV6
|
||||
if (nullptr == t_ping6_pcb) {
|
||||
t_ping6_pcb = raw_new(IP6_NEXTH_ICMP6);
|
||||
|
||||
raw_recv(t_ping6_pcb, t_ping_recv, nullptr); // we cannot register data structure here as we can only register one
|
||||
raw_bind(t_ping6_pcb, IP6_ADDR_ANY);
|
||||
}
|
||||
#endif // USE_IPV6
|
||||
}
|
||||
|
||||
// we have finsihed a ping series, deallocated if no more ongoing
|
||||
|
@ -221,6 +290,10 @@ extern "C" {
|
|||
if (nullptr == ping_head) { // deregister only if no ping is flying
|
||||
raw_remove(t_ping_pcb);
|
||||
t_ping_pcb = nullptr;
|
||||
#ifdef USE_IPV6
|
||||
raw_remove(t_ping6_pcb);
|
||||
t_ping6_pcb = nullptr;
|
||||
#endif // USE_IPV6
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -116,6 +116,7 @@ void EthernetEvent(arduino_event_t *event) {
|
|||
}
|
||||
TasmotaGlobal.rules_flag.eth_connected = 1;
|
||||
TasmotaGlobal.global_state.eth_down = 0;
|
||||
WiFi.saveDNS(); // internal calls to reconnect can zero the DNS servers, save DNS for future use
|
||||
break;
|
||||
|
||||
case ARDUINO_EVENT_ETH_DISCONNECTED:
|
||||
|
|
Loading…
Reference in New Issue