Branch data Line data Source code
1 : : /* SPDX-FileCopyrightText: Copyright 2026 Cozens Software Solutions Limited
2 : : * SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 OR LicenseRef-PolyForm-Internal-Use-1.0.0 OR LicenseRef-COSOSO-Commercial
3 : : */
4 : :
5 : : #include "lwip/opt.h"
6 : :
7 : : /* This component requires lwIP built with DNS. */
8 : : #if LWIP_DNS
9 : :
10 : : #include <stdbool.h>
11 : : #include <stddef.h>
12 : : #include <stdint.h>
13 : :
14 : : #include "SolidSyslogError.h"
15 : : #include "SolidSyslogErrorCategory.h"
16 : : #include "SolidSyslogLwipRawAddressPrivate.h"
17 : : #include "SolidSyslogLwipRawDnsResolverErrors.h"
18 : : #include "SolidSyslogLwipRawDnsResolverPrivate.h"
19 : : #include "SolidSyslogLwipRawMarshalPrivate.h"
20 : : #include "SolidSyslogNullResolver.h"
21 : : #include "SolidSyslogPrival.h"
22 : : #include "SolidSyslogResolverCategories.h"
23 : : #include "SolidSyslogResolverDefinition.h"
24 : : #include "SolidSyslogTransport.h"
25 : : #include "SolidSyslogTunables.h"
26 : : #include "lwip/dns.h"
27 : : #include "lwip/err.h"
28 : : #include "lwip/ip_addr.h"
29 : :
30 : : const struct SolidSyslogErrorSource SolidSyslogLwipRawDnsResolverErrorSource = {
31 : : "LwipRawDnsResolver",
32 : : SolidSyslogResolverErrors_Name
33 : : };
34 : :
35 : : struct SolidSyslogAddress;
36 : :
37 : : /* Per-resolve parameters carried across the marshal hops. The dns_gethostbyname
38 : : * call runs on the lwIP-owning thread; its immediate return code comes back in
39 : : * Err. The async-completion result is published the same way: DoPublishResult
40 : : * runs on the lwIP thread, copies the resolved address into Destination, and
41 : : * reports the outcome in Resolved - so the multi-byte ResolvedIp / ResolvedOk
42 : : * fields the dns_found_callback wrote are read on the thread that wrote them
43 : : * (race-free, with the marshal providing the cross-thread barrier) rather than
44 : : * on the caller's thread off the back of the volatile Done flag. An answer in
45 : : * a family the transports cannot send to is noted in FamilyUnsupported under
46 : : * the same hop and reported once the caller's thread has it back, so the
47 : : * error handler is never entered from the lwIP thread. One struct so the
48 : : * void*-context recovery has a single cast site. */
49 : : struct LwipRawDnsResolverCall
50 : : {
51 : : struct SolidSyslogLwipRawDnsResolver* Self;
52 : : const char* Host;
53 : : struct SolidSyslogLwipRawAddress* Destination;
54 : : uint16_t Port;
55 : : err_t Err;
56 : : bool Resolved;
57 : : bool FamilyUnsupported;
58 : : };
59 : :
60 : : static bool LwipRawDnsResolver_Resolve(
61 : : struct SolidSyslogResolver* base,
62 : : enum SolidSyslogTransport transport,
63 : : const char* host,
64 : : uint16_t port,
65 : : struct SolidSyslogAddress* result
66 : : );
67 : :
68 : : static inline struct SolidSyslogLwipRawDnsResolver* LwipRawDnsResolver_SelfFromBase(struct SolidSyslogResolver* base);
69 : : static inline struct LwipRawDnsResolverCall* LwipRawDnsResolverCallFromContext(void* context);
70 : : static void LwipRawDnsResolver_DoGetHostByName(void* context);
71 : : static void LwipRawDnsResolver_Publish(struct LwipRawDnsResolverCall* call, const ip_addr_t* answer);
72 : : static void LwipRawDnsResolver_FoundCallback(const char* name, const ip_addr_t* ipaddr, void* arg);
73 : : static bool LwipRawDnsResolver_WaitForCallback(struct SolidSyslogLwipRawDnsResolver* self);
74 : : static void LwipRawDnsResolver_DoPublishResult(void* context);
75 : :
76 : 38 : void SolidSyslogLwipRawDnsResolver_Initialise(
77 : : struct SolidSyslogResolver* base,
78 : : const struct SolidSyslogLwipRawDnsResolverConfig* config
79 : : )
80 : : {
81 : 38 : struct SolidSyslogLwipRawDnsResolver* self = LwipRawDnsResolver_SelfFromBase(base);
82 : 38 : base->Resolve = LwipRawDnsResolver_Resolve;
83 : 38 : self->Config = *config;
84 : 38 : self->Done = false;
85 : 38 : self->ResolvedOk = false;
86 : 38 : }
87 : :
88 : 38 : void SolidSyslogLwipRawDnsResolver_Cleanup(struct SolidSyslogResolver* base)
89 : : {
90 : : /* Overwrite the abstract base with the shared NullResolver vtable so
91 : : * use-after-destroy resolves cleanly to a failed-lookup error path
92 : : * rather than a NULL-fn-pointer crash. */
93 : 38 : *base = *SolidSyslogNullResolver_Get();
94 : 38 : }
95 : :
96 : 29 : static bool LwipRawDnsResolver_Resolve(
97 : : struct SolidSyslogResolver* base,
98 : : enum SolidSyslogTransport transport,
99 : : const char* host,
100 : : uint16_t port,
101 : : struct SolidSyslogAddress* result
102 : : )
103 : : {
104 : : (void) transport;
105 : 29 : struct SolidSyslogLwipRawDnsResolver* self = LwipRawDnsResolver_SelfFromBase(base);
106 : 29 : struct SolidSyslogLwipRawAddress* destination = SolidSyslogLwipRawAddress_As(result);
107 : 29 : self->Done = false;
108 : 29 : self->ResolvedOk = false;
109 : :
110 : : /* dns_gethostbyname touches lwIP core state (unlike the numeric resolver's
111 : : * pure ipaddr_aton parse), so it must run under the marshal hop. */
112 : 29 : struct LwipRawDnsResolverCall call = {
113 : : .Self = self,
114 : : .Host = host,
115 : : .Destination = destination,
116 : : .Port = port,
117 : : .Err = ERR_OK,
118 : : .Resolved = false,
119 : : .FamilyUnsupported = false,
120 : : };
121 : 29 : SolidSyslogLwipRaw_Marshal(LwipRawDnsResolver_DoGetHostByName, &call);
122 : :
123 [ + + ]: 29 : if (call.Err == ERR_OK)
124 : : {
125 : : /* Synchronous hit: numeric literal, DNS cache, or local hostlist -
126 : : * dns_gethostbyname wrote ResolvedIp under the marshal hop above and the
127 : : * found_callback never fired, so reading it here (ordered after the
128 : : * marshal returned) is safe and on a single thread. */
129 : 14 : LwipRawDnsResolver_Publish(&call, &self->ResolvedIp);
130 : : }
131 [ + + ]: 15 : else if (call.Err == ERR_INPROGRESS)
132 : : {
133 : : /* Query queued: bound-spin on the caller's thread until the
134 : : * dns_found_callback signals completion via the volatile Done flag (or
135 : : * the deadline passes). The callback wrote the multi-byte ResolvedIp /
136 : : * ResolvedOk on the lwIP thread, so the authoritative read + publish runs
137 : : * back on that thread via DoPublishResult - never off the volatile flag
138 : : * on the caller's thread, which would be an unsynchronised data race. */
139 [ + + ]: 13 : if (LwipRawDnsResolver_WaitForCallback(self))
140 : : {
141 : 9 : SolidSyslogLwipRaw_Marshal(LwipRawDnsResolver_DoPublishResult, &call);
142 : : }
143 : : }
144 : : else
145 : : {
146 : : /* ERR_ARG / any other immediate rejection - Resolved stays false.
147 : : * Terminating else per MISRA 15.7. */
148 : : }
149 : :
150 [ + + ]: 29 : if (call.FamilyUnsupported)
151 : : {
152 : 6 : LwipRawDnsResolver_Report(
153 : : SOLIDSYSLOG_SEVERITY_ERROR,
154 : : SOLIDSYSLOG_CAT_RESOLVER_RESOLVE_FAILED,
155 : : SOLIDSYSLOG_RESOLVER_ERROR_ADDRESS_FAMILY_UNSUPPORTED
156 : : );
157 : : }
158 : 29 : return call.Resolved;
159 : : }
160 : :
161 : : /* IPv4 only, which is what the datagram's and stream's pcbs send to. A stack
162 : : * built with LWIP_IPV6 can answer a name with an IPv6 address, and storing it
163 : : * would give the transports a destination they fail every send to. */
164 : 22 : static void LwipRawDnsResolver_Publish(struct LwipRawDnsResolverCall* call, const ip_addr_t* answer)
165 : : {
166 [ + + ]: 22 : if (LwipRawAddress_IsIpv4(answer))
167 : : {
168 : 16 : call->Destination->Ip = *answer;
169 : 16 : call->Destination->Port = call->Port;
170 : 16 : call->Resolved = true;
171 : : }
172 : : else
173 : : {
174 : 6 : call->FamilyUnsupported = true;
175 : : }
176 : 22 : }
177 : :
178 : 29 : static void LwipRawDnsResolver_DoGetHostByName(void* context)
179 : : {
180 : 29 : struct LwipRawDnsResolverCall* call = LwipRawDnsResolverCallFromContext(context);
181 : 29 : struct SolidSyslogLwipRawDnsResolver* self = call->Self;
182 : : /* Asked for lwIP's default family, not IPv4 alone: told IPv4 only, lwIP
183 : : * keeps an IPv6 literal or entry to itself and queries the name afresh,
184 : : * so the lookup fails later as a plain miss. Asked for either, it hands
185 : : * the IPv6 answer over, and Publish refuses it with the family named. */
186 : 29 : call->Err = dns_gethostbyname(call->Host, &self->ResolvedIp, LwipRawDnsResolver_FoundCallback, self);
187 : 29 : }
188 : :
189 : : /* lwIP fires this on the tcpip thread when an async lookup completes. ipaddr is
190 : : * the resolved address, or NULL on failure. Touches no lwIP API, so it needs no
191 : : * marshalling; sets the flags the bounded spin (WaitForCallback) polls. */
192 : 9 : static void LwipRawDnsResolver_FoundCallback(const char* name, const ip_addr_t* ipaddr, void* arg)
193 : : {
194 : : (void) name;
195 : 9 : struct SolidSyslogLwipRawDnsResolver* self = (struct SolidSyslogLwipRawDnsResolver*) arg;
196 [ + + ]: 9 : if (ipaddr != NULL)
197 : : {
198 : 8 : self->ResolvedIp = *ipaddr;
199 : 8 : self->ResolvedOk = true;
200 : : }
201 : 9 : self->Done = true;
202 : 9 : }
203 : :
204 : : /* Bounded async-resolve spin: each iteration sleeps via the integrator-injected
205 : : * Sleep so lwIP's DNS timer / RX paths get cycles to advance the query. Runs on
206 : : * the caller's thread - never the lwIP thread. Exits when the callback has set
207 : : * the volatile Done flag (success or NULL-delivery failure) or the deadline
208 : : * elapses (timeout). Returns whether completion was observed; the authoritative
209 : : * success/address read happens under the marshal in DoPublishResult, so this
210 : : * deliberately does NOT read the non-volatile ResolvedOk / ResolvedIp here. */
211 : 13 : static bool LwipRawDnsResolver_WaitForCallback(struct SolidSyslogLwipRawDnsResolver* self)
212 : : {
213 : 13 : const uint32_t pollMs = (uint32_t) SOLIDSYSLOG_LWIP_RAW_DNS_RESOLVE_POLL_MS;
214 : 13 : const uint32_t deadlineMs = (uint32_t) SOLIDSYSLOG_DNS_RESOLVE_TIMEOUT_MS;
215 : 13 : uint32_t elapsedMs = 0U;
216 [ + + + + ]: 2022 : while (!self->Done && (elapsedMs < deadlineMs))
217 : : {
218 : 2009 : self->Config.Sleep(pollMs);
219 : 2009 : elapsedMs += pollMs;
220 : : }
221 [ + + ]: 13 : if (!self->Done)
222 : : {
223 : 4 : LwipRawDnsResolver_Report(
224 : : SOLIDSYSLOG_SEVERITY_WARNING,
225 : : SOLIDSYSLOG_CAT_RESOLVER_RESOLVE_FAILED,
226 : : SOLIDSYSLOG_RESOLVER_ERROR_RESOLVE_TIMEOUT
227 : : );
228 : : }
229 : 13 : return self->Done;
230 : : }
231 : :
232 : : /* Marshalled result read for the async path: runs on the lwIP thread (the same
233 : : * thread the dns_found_callback wrote ResolvedOk / ResolvedIp on), so the read
234 : : * is race-free and the marshal hop carries the cross-thread happens-before that
235 : : * the volatile Done flag alone cannot. Publishes into the caller-owned
236 : : * Destination and reports the outcome in Resolved (read after the marshal
237 : : * returns, ordered by it). */
238 : 9 : static void LwipRawDnsResolver_DoPublishResult(void* context)
239 : : {
240 : 9 : struct LwipRawDnsResolverCall* call = LwipRawDnsResolverCallFromContext(context);
241 : 9 : struct SolidSyslogLwipRawDnsResolver* self = call->Self;
242 [ + + ]: 9 : if (self->ResolvedOk)
243 : : {
244 : 8 : LwipRawDnsResolver_Publish(call, &self->ResolvedIp);
245 : : }
246 : 9 : }
247 : :
248 : 67 : static inline struct SolidSyslogLwipRawDnsResolver* LwipRawDnsResolver_SelfFromBase(struct SolidSyslogResolver* base)
249 : : {
250 : 67 : return (struct SolidSyslogLwipRawDnsResolver*) base;
251 : : }
252 : :
253 : 38 : static inline struct LwipRawDnsResolverCall* LwipRawDnsResolverCallFromContext(void* context)
254 : : {
255 : 38 : return (struct LwipRawDnsResolverCall*) context;
256 : : }
257 : :
258 : : #else
259 : :
260 : : /* ISO C forbids an empty translation unit. */
261 : : typedef int LwipRawDnsResolver_EmptyTranslationUnit;
262 : :
263 : : #endif /* LWIP_DNS */
|