DeterministicESPAsyncWebServer v6.28.0
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
dns_resolver.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file dns_resolver.cpp
6 * @brief IPv4 classifier / verifier (pure) + lwIP DNS resolve (ESP32).
7 *
8 * The resolve marshals dns_gethostbyname into tcpip_thread and polls a done flag
9 * with a deadline - the same cross-thread pattern the http_client uses.
10 */
11
13
14#if DETWS_ENABLE_DNS_RESOLVER
15
16DetwsIpClass detws_dns_classify(uint32_t ip)
17{
18 if (ip == 0u)
19 return DetwsIpClass::DETWS_IP_UNSPECIFIED;
20 if (ip == 0xFFFFFFFFu)
21 return DetwsIpClass::DETWS_IP_BROADCAST;
22 uint8_t a = (uint8_t)((ip >> 24) & 0xFF);
23 uint8_t b = (uint8_t)((ip >> 16) & 0xFF);
24 if (a == 127)
25 return DetwsIpClass::DETWS_IP_LOOPBACK;
26 if (a == 10)
27 return DetwsIpClass::DETWS_IP_PRIVATE;
28 if (a == 172 && b >= 16 && b <= 31)
29 return DetwsIpClass::DETWS_IP_PRIVATE;
30 if (a == 192 && b == 168)
31 return DetwsIpClass::DETWS_IP_PRIVATE;
32 if (a == 169 && b == 254)
33 return DetwsIpClass::DETWS_IP_LINKLOCAL;
34 if (a >= 224 && a <= 239)
35 return DetwsIpClass::DETWS_IP_MULTICAST;
36 return DetwsIpClass::DETWS_IP_PUBLIC;
37}
38
39bool detws_dns_verify(uint32_t ip)
40{
41 switch (detws_dns_classify(ip))
42 {
43 case DetwsIpClass::DETWS_IP_UNSPECIFIED: // 0.0.0.0 - blocked / no answer
44 case DetwsIpClass::DETWS_IP_BROADCAST: // 255.255.255.255 - never a host
45 case DetwsIpClass::DETWS_IP_LOOPBACK: // 127.x - DNS-rebinding to localhost
46 case DetwsIpClass::DETWS_IP_MULTICAST: // 224-239 - never an A-record host
47 return false;
48 default:
49 return true; // private / link-local / public are plausible
50 }
51}
52
53#ifdef ARDUINO
54
55#include "lwip/def.h"
56#include "lwip/dns.h"
57#include "lwip/ip_addr.h"
58#include "lwip/priv/tcpip_priv.h"
59#include "services/clock.h" // detws_millis() - the single pluggable monotonic source
60#include <Arduino.h>
61
62namespace
63{
64// All DNS-resolve binding state, owned by one instance (internal linkage): the resolved
65// address plus the done/ok flags the lwIP callback sets, grouped so it is one named owner,
66// unreachable cross-TU. The flags are volatile: the callback runs on tcpip_thread while the
67// resolve loop polls them.
68struct DnsResolverCtx
69{
70 ip_addr_t addr;
71 volatile bool done = false;
72 volatile bool ok = false;
73};
74DnsResolverCtx s_dr;
75
76struct DnsCall
77{
78 struct tcpip_api_call_data base;
79 const char *host;
80};
81
82void dns_cb(const char *name, const ip_addr_t *addr, void *arg)
83{
84 (void)name;
85 (void)arg;
86 if (addr)
87 {
88 s_dr.addr = *addr;
89 s_dr.ok = true;
90 }
91 s_dr.done = true;
92}
93
94err_t do_dns(struct tcpip_api_call_data *c)
95{
96 const char *host = ((DnsCall *)c)->host;
97 err_t e = dns_gethostbyname(host, &s_dr.addr, dns_cb, nullptr);
98 if (e == ERR_OK) // already cached
99 {
100 s_dr.ok = true;
101 s_dr.done = true;
102 }
103 else if (e != ERR_INPROGRESS) // hard failure
104 s_dr.done = true;
105 return ERR_OK;
106}
107
108uint32_t to_host_order(const ip_addr_t *a)
109{
110 return lwip_ntohl(ip4_addr_get_u32(ip_2_ip4(a)));
111}
112} // namespace
113
114bool detws_dns_resolve(const char *host, uint32_t *out_ip)
115{
116 if (!host || !out_ip)
117 return false;
118
119 ip_addr_t literal;
120 if (ipaddr_aton(host, &literal)) // dotted-quad fast path, no DNS
121 {
122 *out_ip = to_host_order(&literal);
123 return true;
124 }
125
126 s_dr.done = false;
127 s_dr.ok = false;
128 DnsCall k;
129 memset(&k, 0, sizeof(k));
130 k.host = host;
131 tcpip_api_call(do_dns, &k.base); // resolve in the lwIP thread
132
133 uint32_t deadline = detws_millis() + DETWS_DNS_TIMEOUT_MS;
134 while (!s_dr.done && (int32_t)(deadline - detws_millis()) > 0)
135 dwsdelay(5);
136
137 if (!s_dr.ok)
138 return false;
139 *out_ip = to_host_order(&s_dr.addr);
140 return true;
141}
142
143#else // host build - no real resolver; a host test can inject a synthetic answer
144
145namespace
146{
147// The injected synthetic answer, owned by one instance (internal linkage): a host test sets
148// it via detws_dns_test_set_resolve() and detws_dns_resolve() returns it, grouped so it is one
149// named owner rather than loose file-scope mutables.
150struct DnsTestCtx
151{
152 bool ok = false;
153 uint32_t ip = 0;
154};
155DnsTestCtx s_dns_test;
156} // namespace
157
158void detws_dns_test_set_resolve(bool ok, uint32_t ip)
159{
160 s_dns_test.ok = ok;
161 s_dns_test.ip = ip;
162}
163bool detws_dns_resolve(const char *, uint32_t *out_ip)
164{
165 if (!s_dns_test.ok)
166 return false;
167 if (out_ip)
168 *out_ip = s_dns_test.ip;
169 return true;
170}
171
172#endif // ARDUINO
173
174bool detws_dns_resolve_verified(const char *host, uint32_t *out_ip)
175{
176 uint32_t ip = 0;
177 if (!detws_dns_resolve(host, &ip))
178 return false;
179 if (!detws_dns_verify(ip))
180 return false;
181 if (out_ip)
182 *out_ip = ip;
183 return true;
184}
185
186#endif // DETWS_ENABLE_DNS_RESOLVER
#define DETWS_DNS_TIMEOUT_MS
DNS resolve timeout in milliseconds.
Pluggable monotonic clock for all library timing.
uint32_t detws_millis(void)
The library's monotonic time at 1000 Hz (milliseconds).
Definition clock.h:71
void dwsdelay(uint32_t ms)
Block for at least ms milliseconds - the library's single delay primitive.
Definition clock.h:87
DNS resolver with answer verification (DETWS_ENABLE_DNS_RESOLVER).