An Arduino library for representing a network host identified by either an IPv4 address or a fully qualified domain name (FQDN).
- Stores either an IPv4 address or an FQDN — but not both simultaneously
- RFC 1035 / RFC 3696 compliant FQDN validation
- IPv4 address validation with octet range checking
- Built-in DNS resolution on ESP32, ESP8266 and AVR platforms
- Custom resolver callback support for other platforms
- Thread-safe via
std::timed_mutexwith configurable timeout on ESP32 - No mutex is used on single-threaded platforms (AVR, ESP8266)
- Lightweight: no dynamic memory allocation for the host data itself
| Platform | DNS resolution |
|---|---|
| ESP32 (Arduino Core 3.x) | Network.hostByName |
| ESP8266 | WiFi.hostByName |
| AVR (Ethernet shield)* | DNSClient::getHostByName |
| Other | Custom resolver required |
*On the Arduino AVR platform, functionality is limited due to scarce resources.
For example, on an ATmega328-based Uno, a 254-byte FQDN can already take up too
much memory. The situation is better on the ATmega2560-based Mega.
There are preprocessor macros available to help resolve this issue:
HOST_FQDN_LABEL_LEN and HOST_FQDN_LEN. See Configuration
for details.
| Platform | Dependencies |
|---|---|
| ESP32 | ESP32 Arduino Core 3.x |
| ESP8266 | ESP8266 Arduino Core 3.x |
| AVR | Ethernet library |
Install via Library Manager:
Search for Host in the Library Manager (Sketch → Include Library → Manage Libraries).
Install manually:
- Download this repository as a ZIP file
- In Arduino IDE: Sketch → Include Library → Add .ZIP Library
lib_deps =
soosp/HostCopy the Host.h file into your project or library folder.
#include <Host.h>
// Empty host
Host h;
// From IP address
Host h1(IPAddress(192, 168, 1, 1));
// From string — parsed as IP or FQDN automatically
Host h2("example.com");
Host h3("192.168.1.1");
// Set/get
h.setIP(IPAddress(10, 0, 0, 1));
h.setFqdn("mqtt.example.com");
// Resolve to IP (performs DNS lookup if FQDN is stored)
IPAddress ip = h2.getIP();
// Serialize
char buf[64];
h.toStr(buf, sizeof(buf)); // "192.168.1.1" or "example.com"
// Parse
h.fromStr("192.168.1.1"); // stored as IP
h.fromStr("example.com"); // stored as FQDNif (Host::isValidFqdn("example.com")) { ... }
if (Host::isValidIp("192.168.1.1")) { ... }A custom resolver can be passed to any constructor as the last parameter.
The function must match the ResolverFn signature:
bool myResolver(const char* fqdn, IPAddress& ip) {
// your DNS implementation
return true;
}
Host h("example.com", myResolver);On ESP32, an mDNS resolver can be used for .local hostnames:
#include <ESPmDNS.h>
bool mdnsResolver(const char* fqdn, IPAddress& ip) {
ip = MDNS.queryHost(fqdn);
return ip != IPAddress(0,0,0,0);
}
Host h("mydevice.local", mdnsResolver);Or combined with regular DNS as a fallback:
#include <ESPmDNS.h>
bool mdnsWithFallback(const char* fqdn, IPAddress& ip) {
// Try mDNS first (fast, local network)
ip = MDNS.queryHost(fqdn);
if (ip != IPAddress(0,0,0,0)) return true;
// Fall back to regular DNS
return Network.hostByName(fqdn, ip) == 1;
}
Host h("mydevice.local", mdnsWithFallback);| Constant | Value | Description |
|---|---|---|
MUTEX_TIMEOUT |
1000 | Default mutex acquisition timeout in milliseconds. |
MAX_FQDN_LABEL_LEN |
63 | Maximum length of a single FQDN label |
MAX_FQDN_LABEL_SIZE |
64 | Buffer size for a single FQDN label including null terminator |
MAX_FQDN_LEN |
253 | Maximum total FQDN length |
MAX_FQDN_SIZE |
254 | Buffer size for FQDN including null terminator |
DEFAULT_DNS_CACHE_TTL_MS |
300000 | Default DNS cache TTL |
The default mutex timeout is overridable via the HOST_MUTEX_TIMEOUT
preprocessor macro.
Although the RFC specifications define an FQDN as 253 characters and labels
as 63 characters, these values can consume excessive memory on
resource-constrained systems (e.g., Atmel AVR). Therefore, these lengths are
configurable via preprocessor macros HOST_FQDN_LABEL_LEN and HOST_FQDN_LEN
to allow smaller footprints.
The default resolver cache TTL is overridable via the HOST_DEFAULT_DNS_CACHE_TTL_MS
preprocessor macro.
Some macros set fixed buffer sizes, so every translation unit that
includes a library header must see the same values. In a single-file sketch,
define them before the first #include. In a multi-file project they must be
defined globally — otherwise the differing struct layouts across .cpp files
are an ODR violation (undefined behaviour).
-
PlatformIO (all platforms) — in
platformio.ini:build_flags = -D HOST_FQDN_LEN=63 -D HOST_DEFAULT_DNS_CACHE_TTL_MS=150000
-
Arduino IDE, ESP8266 — add a file named
<SketchName>.ino.globals.hnext to your.ino; the ESP8266 core force-includes it into every source file:// MySketch.ino.globals.h #define HOST_FQDN_LEN 63 #define HOST_DEFAULT_DNS_CACHE_TTL_MS 150000
-
Arduino IDE, ESP32 — add a file named
build_opt.hin the sketch folder. The core passes it to the compiler as a response file for every translation unit, so it holds compiler flags, not#defines (despite the.hname):-DHOST_FQDN_LEN=63 -DHOST_DEFAULT_DNS_CACHE_TTL_MS=150000
-
Arduino IDE, AVR — there is no per-sketch global mechanism. Either keep the sketch to a single
.ino(define before the first include), or pass the flags viaarduino-cli --build-property "compiler.cpp.extra_flags=-DHOST_FQDN_LEN=63 …". For a multi-file AVR project, PlatformIO is the simplest route.
The ESP8266
.ino.globals.hand the ESP32build_opt.hare different mechanisms — ESP32 does not read.ino.globals.h, and the ESP8266/*@create-file:build.opt@ … @end*/block has no ESP32 equivalent (put the raw flags straight intobuild_opt.hinstead).
Defining a macro before the include in only your
.inoaffects that file only — other.cppfiles use the defaults. That mismatch is the multi-TU pitfall the global methods above avoid.
using ResolverFn = bool(*)(const char* fqdn, IPAddress& ip);Function pointer type for custom DNS resolver callbacks. The function receives a
null-terminated FQDN string and must write the resolved IP into ip.
Returns true on success, false on failure.
Host(ResolverFn resolver = _defaultResolver)Constructs an empty Host (IP 0.0.0.0, no FQDN).
explicit Host(const IPAddress ip, ResolverFn resolver = _defaultResolver)Constructs a Host from an IPv4 address.
explicit Host(const char* str, ResolverFn resolver = _defaultResolver)Constructs a Host from a string. The string is first tried as a dotted-decimal IPv4 address, then as an FQDN. If neither is valid, the Host is initialized empty.
All constructors accept an optional resolver parameter. On ESP32 the default
resolver uses Network.hostByName, on ESP8266 WiFi.hostByName, on AVR
DNSClient::getHostByName.
On other platforms a stub resolver is used that always returns false; a custom
resolver must be provided for DNS resolution to work.
Copy construction and copy assignment are disabled.
Returns true if the Host holds neither an IP address nor an FQDN (i.e. it was
default-constructed, or parsing failed). Also returns true if the internal
mutex could not be acquired within MUTEX_TIMEOUT milliseconds.
Returns the IP address of the host. If an IP address is stored directly, it is returned immediately. If only an FQDN is stored, a DNS lookup is performed via the resolver function. The DNS lookup is intentionally done outside the mutex to avoid blocking other threads during network I/O. To reduce the number of DNS lookups it stores the resolved IP address and returns it until the set TTL expires. Once the TTL has expired, it performs a new query.
Returns 0.0.0.0 if the mutex could not be acquired or DNS resolution failed.
Sets the host address to the given IPv4 address and clears any stored FQDN.
Returns true on success, false if the mutex could not be acquired.
Copies the stored FQDN into buf. At most len bytes are written, including
the null terminator. Returns true if the FQDN fit entirely into the buffer,
false on truncation or mutex failure.
Sets the host address to the given FQDN and clears the stored IP.
The FQDN is validated against RFC 1035 / RFC 3696 rules before storing.
Returns true on success, false if the FQDN is not RFC-conformant or
the mutex could not be acquired.
Returns the TTL of DNS cache in miliseconds, or 0 on mutex failure.
Sets the TTL of DNS cache in miliseconds. Returns true on success,
false on mutex failure.
Serializes the host to a human-readable string. Writes a dotted-decimal IP
address (e.g. "192.168.1.1") if no FQDN is set, otherwise writes the FQDN.
At most len bytes are written, including the null terminator. Empty host
object results IP address "0.0.0.0".
Returns true if the result fit entirely, false on truncation or mutex
failure.
Parses a string into a host address. Empty input string results empty host
object. The string is first tried as a dotted-decimal IPv4 address with all
octets in range [0, 255]; if that fails, it is tried as an RFC-conformant FQDN.
The previously stored address is replaced only on success.
Returns true on success, false if the string is neither a valid IP address
nor a valid FQDN, or if the mutex could not be acquired.
Validates an FQDN string against RFC 1035 / RFC 3696 rules:
- Total length must not exceed 253 characters (excluding any trailing dot)
- Each dot-separated label must be 1–63 characters long
- Labels may only contain ASCII letters, digits, and hyphens
- Labels must not start or end with a hyphen
- The last label (TLD) must not be all-numeric
Returns true if the string is a valid FQDN, false otherwise.
Validates and optionally parses a string as dotted-decimal IPv4 address.
All four octets must be present and in the range [0, 255].
Extra characters after the fourth octet (e.g. "1.2.3.4.5"), and any non
number and non dot character in the string cause the validation to fail.
Returns true if the string is a valid dotted-decimal IPv4 address, false
otherwise.
If parameter ip is specified, the IP address in the string will be parsed
into it.
Validates a string as a dotted-decimal IPv4 address. Retained for compatibility
reasons. Uses parseIp in the backgroud.
Returns true if the string is a valid IPv4 address, false otherwise.
All public methods except isValidFqdn and isValidIp are thread-safe on
non-AVR platforms. The bool-returning methods return false on mutex
acquisition timeout.
MIT — see LICENSE for details.