Add user control over DNS timeout reducing blocking

Add command ``DnsTimeout 100..20000`` to change default DNS timeout from 1000 msec blocking if no DNS server found
This commit is contained in:
Theo Arends 2022-06-19 17:57:43 +02:00
parent cf5c8cf566
commit c988ba1645
14 changed files with 423 additions and 19 deletions

View File

@ -3,9 +3,9 @@ All notable changes to this project will be documented in this file.
## [Unreleased] - Development
## [12.0.1.1]
## [12.0.1.2]
### Added
- Command ``DnsTimeout 100..20000`` to change default DNS timeout from 1000 msec blocking if no DNS server found
### Changed

View File

@ -107,8 +107,9 @@ The latter links can be used for OTA upgrades too like ``OtaUrl http://ota.tasmo
[Complete list](BUILDS.md) of available feature and sensors.
## Changelog v12.0.1.1
## Changelog v12.0.1.2
### Added
- Command ``DnsTimeout 100..20000`` to change default DNS timeout from 1000 msec blocking if no DNS server found
### Breaking Changed

View File

@ -0,0 +1,7 @@
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

View File

@ -0,0 +1,337 @@
/*
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(uint16_t aTimeout) {
iTimeout = aTimeout;
}
int DNSClient::getHostByName(const char* aHostname, IPAddress& aResult) {
int ret =0;
// See if it's a numeric IP address
if (aResult.fromString(aHostname)) {
// It is, our work here is done
return 1;
}
// Check we've got a valid DNS server to use
if ((0xFFFFFFFF == (uint32_t)iDNSServer) || (0 == (uint32_t)iDNSServer)) {
return INVALID_SERVER;
}
// 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;
}
uint16_t DNSClient::BuildRequest(const char* aName) {
// Build header
// 1 1 1 1 1 1
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | 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 1;
}
uint16_t DNSClient::ProcessResponse(uint16_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 (uint16_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 (int 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 (uint16_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 (int 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 (uint16_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;
}

View File

@ -0,0 +1,42 @@
/*
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(uint16_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:
uint16_t BuildRequest(const char* aName);
uint16_t ProcessResponse(uint16_t aTimeout, IPAddress& aAddress);
IPAddress iDNSServer;
uint16_t iRequestId;
uint16_t iTimeout = 1000;
WiFiUDP iUdp;
};
#endif

View File

@ -317,6 +317,7 @@
#define D_WCFG_5_WAIT "Wait"
#define D_WCFG_6_SERIAL "Serial"
#define D_WCFG_7_WIFIMANAGER_RESET_ONLY "ManagerRst"
#define D_CMND_DNSTIMEOUT "DnsTimeout"
#define D_CMND_DEVICENAME "DeviceName"
#define D_CMND_FRIENDLYNAME "FriendlyName"
#define D_CMND_FN "FN"

View File

@ -268,6 +268,10 @@ String EthernetMacAddress(void);
#define TASM_FILE_AUTOEXEC "/autoexec.bat" // Commands executed after restart
#define TASM_FILE_CONFIG "/config.sys" // Settings executed after restart
#ifndef DNS_TIMEOUT
#define DNS_TIMEOUT 1000 // Milliseconds
#endif
#ifndef MQTT_MAX_PACKET_SIZE
#define MQTT_MAX_PACKET_SIZE 1200 // Bytes
//#define MQTT_MAX_PACKET_SIZE 2048 // Bytes

View File

@ -675,8 +675,9 @@ typedef struct {
uint8_t knx_CB_registered; // 4A8 Number of Group Address to write
uint8_t switchmode[MAX_SWITCHES_SET]; // 4A9
uint8_t free_4c5[5]; // 4C5
uint8_t free_4c5[3]; // 4C5
uint16_t dns_timeout; // 4C8
uint8_t ds3502_state[MAX_DS3502]; // 4CA
uint16_t influxdb_port; // 4CE
power_t interlock[MAX_INTERLOCKS_SET]; // 4D0 MAX_INTERLOCKS = MAX_RELAYS / 2

View File

@ -20,6 +20,6 @@
#ifndef _TASMOTA_VERSION_H_
#define _TASMOTA_VERSION_H_
const uint32_t VERSION = 0x0C000101; // 12.0.1.1
const uint32_t VERSION = 0x0C000102; // 12.0.1.2
#endif // _TASMOTA_VERSION_H_

View File

@ -79,6 +79,7 @@
#define WIFI_CONFIG_TOOL WIFI_RETRY // [WifiConfig] Default tool if Wi-Fi fails to connect (default option: 4 - WIFI_RETRY)
// (WIFI_RESTART, WIFI_MANAGER, WIFI_RETRY, WIFI_WAIT, WIFI_SERIAL, WIFI_MANAGER_RESET_ONLY)
// The configuration can be changed after first setup using WifiConfig 0, 2, 4, 5, 6 and 7.
#define DNS_TIMEOUT 1000 // [DnsTimeout] Number of ms before DNS timeout
#define WIFI_ARP_INTERVAL 60 // [SetOption41] Send gratuitous ARP interval
#define WIFI_SCAN_AT_RESTART false // [SetOption56] Scan Wi-Fi network at restart for configured AP's
#define WIFI_SCAN_REGULARLY true // [SetOption57] Scan Wi-Fi network every 44 minutes for configured AP's

View File

@ -35,6 +35,7 @@
// 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
@ -158,6 +159,7 @@ struct XDRVMAILBOX {
char *command;
} XdrvMailbox;
DNSClient DnsClient;
WiFiUDP PortUdp; // UDP Syslog and Alexa
#ifdef ESP32
@ -588,6 +590,7 @@ 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)"),

View File

@ -926,6 +926,7 @@ void SettingsDefaultSet2(void) {
flag3.use_wifi_scan |= WIFI_SCAN_AT_RESTART;
flag3.use_wifi_rescan |= WIFI_SCAN_REGULARLY;
Settings->wifi_output_power = 170;
Settings->dns_timeout = DNS_TIMEOUT;
Settings->param[P_ARP_GRATUITOUS] = WIFI_ARP_INTERVAL;
ParseIPv4(&Settings->ipv4_address[0], PSTR(WIFI_IP_ADDRESS));
ParseIPv4(&Settings->ipv4_address[1], PSTR(WIFI_GATEWAY));
@ -1540,6 +1541,9 @@ void SettingsDelta(void) {
Settings->weight_offset = Settings->energy_frequency_calibration * Settings->weight_calibration;
#endif
}
if (Settings->version < 0x0C000102) { // 12.0.1.2
Settings->dns_timeout = DNS_TIMEOUT;
}
Settings->version = VERSION;
SettingsSave(1);

View File

@ -30,7 +30,7 @@ const char kTasmotaCommands[] PROGMEM = "|" // No prefix
D_CMND_MODULE "|" D_CMND_MODULES "|" D_CMND_GPIO "|" D_CMND_GPIOS "|" D_CMND_TEMPLATE "|" D_CMND_PWM "|" D_CMND_PWMFREQUENCY "|" D_CMND_PWMRANGE "|"
D_CMND_BUTTONDEBOUNCE "|" D_CMND_SWITCHDEBOUNCE "|" D_CMND_SYSLOG "|" D_CMND_LOGHOST "|" D_CMND_LOGPORT "|"
D_CMND_SERIALBUFFER "|" D_CMND_SERIALSEND "|" D_CMND_BAUDRATE "|" D_CMND_SERIALCONFIG "|" D_CMND_SERIALDELIMITER "|"
D_CMND_IPADDRESS "|" D_CMND_NTPSERVER "|" D_CMND_AP "|" D_CMND_SSID "|" D_CMND_PASSWORD "|" D_CMND_HOSTNAME "|" D_CMND_WIFICONFIG "|" D_CMND_WIFI "|"
D_CMND_IPADDRESS "|" D_CMND_NTPSERVER "|" D_CMND_AP "|" D_CMND_SSID "|" D_CMND_PASSWORD "|" D_CMND_HOSTNAME "|" D_CMND_WIFICONFIG "|" D_CMND_WIFI "|" D_CMND_DNSTIMEOUT "|"
D_CMND_DEVICENAME "|" D_CMND_FN "|" D_CMND_FRIENDLYNAME "|" D_CMND_SWITCHMODE "|" D_CMND_INTERLOCK "|" D_CMND_TELEPERIOD "|" D_CMND_RESET "|" D_CMND_TIME "|" D_CMND_TIMEZONE "|" D_CMND_TIMESTD "|"
D_CMND_TIMEDST "|" D_CMND_ALTITUDE "|" D_CMND_LEDPOWER "|" D_CMND_LEDSTATE "|" D_CMND_LEDMASK "|" D_CMND_LEDPWM_ON "|" D_CMND_LEDPWM_OFF "|" D_CMND_LEDPWM_MODE "|"
D_CMND_WIFIPOWER "|" D_CMND_TEMPOFFSET "|" D_CMND_HUMOFFSET "|" D_CMND_SPEEDUNIT "|" D_CMND_GLOBAL_TEMP "|" D_CMND_GLOBAL_HUM"|" D_CMND_SWITCHTEXT "|"
@ -65,7 +65,7 @@ void (* const TasmotaCommand[])(void) PROGMEM = {
&CmndModule, &CmndModules, &CmndGpio, &CmndGpios, &CmndTemplate, &CmndPwm, &CmndPwmfrequency, &CmndPwmrange,
&CmndButtonDebounce, &CmndSwitchDebounce, &CmndSyslog, &CmndLoghost, &CmndLogport,
&CmndSerialBuffer, &CmndSerialSend, &CmndBaudrate, &CmndSerialConfig, &CmndSerialDelimiter,
&CmndIpAddress, &CmndNtpServer, &CmndAp, &CmndSsid, &CmndPassword, &CmndHostname, &CmndWifiConfig, &CmndWifi,
&CmndIpAddress, &CmndNtpServer, &CmndAp, &CmndSsid, &CmndPassword, &CmndHostname, &CmndWifiConfig, &CmndWifi, &CmndDnsTimeout,
&CmndDevicename, &CmndFriendlyname, &CmndFriendlyname, &CmndSwitchMode, &CmndInterlock, &CmndTeleperiod, &CmndReset, &CmndTime, &CmndTimezone, &CmndTimeStd,
&CmndTimeDst, &CmndAltitude, &CmndLedPower, &CmndLedState, &CmndLedMask, &CmndLedPwmOn, &CmndLedPwmOff, &CmndLedPwmMode,
&CmndWifiPower, &CmndTempOffset, &CmndHumOffset, &CmndSpeedUnit, &CmndGlobalTemp, &CmndGlobalHum, &CmndSwitchText,
@ -2277,6 +2277,15 @@ void CmndWifi(void)
Response_P(PSTR("{\"" D_JSON_WIFI "\":\"%s\",\"" D_JSON_WIFI_MODE "\":\"11%c\"}"), GetStateText(Settings->flag4.network_wifi), pgm_read_byte(&kWifiPhyMode[WiFi.getPhyMode() & 0x3]) );
}
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);
}
#ifdef USE_I2C
void CmndI2cScan(void)
{

View File

@ -729,20 +729,14 @@ void wifiKeepAlive(void) {
}
#endif // ESP8266
int WifiHostByName(const char* aHostname, IPAddress& aResult) {
bool WifiHostByName(const char* aHostname, IPAddress& aResult) {
// Use this instead of WiFi.hostByName or connect(host_name,.. to block less if DNS server is not found
aResult = (uint32_t)(0);
if (aResult.fromString(aHostname)) {
// Host name is already an IP address so use it!
return 1;
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)) {
return true;
}
else if (WiFi.hostByName(aHostname, aResult)) {
// Host name resolved
if (0xFFFFFFFF != (uint32_t)aResult) {
return 1;
}
}
return 0;
return false;
}
void WifiPollNtp() {