TLA Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 : // Copyright (c) 2026 Steve Gerbino
4 : // Copyright (c) 2026 Michael Vandeberg
5 : //
6 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
7 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
8 : //
9 : // Official repository: https://github.com/cppalliance/corosio
10 : //
11 :
12 : #ifndef BOOST_COROSIO_RESOLVER_HPP
13 : #define BOOST_COROSIO_RESOLVER_HPP
14 :
15 : #include <boost/corosio/detail/config.hpp>
16 : #include <boost/corosio/detail/op_base.hpp>
17 : #include <boost/corosio/endpoint.hpp>
18 : #include <boost/corosio/io/io_object.hpp>
19 : #include <boost/capy/io_result.hpp>
20 : #include <boost/corosio/resolver_results.hpp>
21 : #include <boost/capy/ex/executor_ref.hpp>
22 : #include <boost/capy/ex/execution_context.hpp>
23 : #include <boost/capy/ex/io_env.hpp>
24 : #include <boost/capy/concept/executor.hpp>
25 :
26 : #include <system_error>
27 :
28 : #include <cassert>
29 : #include <concepts>
30 : #include <coroutine>
31 : #include <stop_token>
32 : #include <string>
33 : #include <string_view>
34 : #include <type_traits>
35 :
36 : namespace boost::corosio {
37 :
38 : /** Bitmask flags for resolver queries.
39 :
40 : These flags correspond to the hints parameter of getaddrinfo.
41 : */
42 : enum class resolve_flags : unsigned int
43 : {
44 : /// No flags.
45 : none = 0,
46 :
47 : /// Indicate that returned endpoint is intended for use as a locally
48 : /// bound socket endpoint.
49 : passive = 0x01,
50 :
51 : /// Host name should be treated as a numeric string defining an IPv4
52 : /// or IPv6 address and no name resolution should be attempted.
53 : numeric_host = 0x04,
54 :
55 : /// Service name should be treated as a numeric string defining a port
56 : /// number and no name resolution should be attempted.
57 : numeric_service = 0x08,
58 :
59 : /// Only return IPv4 addresses if a non-loopback IPv4 address is
60 : /// configured for the system. Only return IPv6 addresses if a
61 : /// non-loopback IPv6 address is configured for the system.
62 : address_configured = 0x20,
63 :
64 : /// If the query protocol family is specified as IPv6, return
65 : /// IPv4-mapped IPv6 addresses on finding no IPv6 addresses.
66 : v4_mapped = 0x800,
67 :
68 : /// If used with v4_mapped, return all matching IPv6 and IPv4 addresses.
69 : all_matching = 0x100
70 : };
71 :
72 : /** Combine two resolve_flags. */
73 : inline resolve_flags
74 HIT 17 : operator|(resolve_flags a, resolve_flags b) noexcept
75 : {
76 : return static_cast<resolve_flags>(
77 17 : static_cast<unsigned int>(a) | static_cast<unsigned int>(b));
78 : }
79 :
80 : /** Combine two resolve_flags. */
81 : inline resolve_flags&
82 1 : operator|=(resolve_flags& a, resolve_flags b) noexcept
83 : {
84 1 : a = a | b;
85 1 : return a;
86 : }
87 :
88 : /** Intersect two resolve_flags. */
89 : inline resolve_flags
90 199 : operator&(resolve_flags a, resolve_flags b) noexcept
91 : {
92 : return static_cast<resolve_flags>(
93 199 : static_cast<unsigned int>(a) & static_cast<unsigned int>(b));
94 : }
95 :
96 : /** Intersect two resolve_flags. */
97 : inline resolve_flags&
98 1 : operator&=(resolve_flags& a, resolve_flags b) noexcept
99 : {
100 1 : a = a & b;
101 1 : return a;
102 : }
103 :
104 : /** Bitmask flags for reverse resolver queries.
105 :
106 : These flags correspond to the flags parameter of getnameinfo.
107 : */
108 : enum class reverse_flags : unsigned int
109 : {
110 : /// No flags.
111 : none = 0,
112 :
113 : /// Return the numeric form of the hostname instead of its name.
114 : numeric_host = 0x01,
115 :
116 : /// Return the numeric form of the service name instead of its name.
117 : numeric_service = 0x02,
118 :
119 : /// Return an error if the hostname cannot be resolved.
120 : name_required = 0x04,
121 :
122 : /// Lookup for datagram (UDP) service instead of stream (TCP).
123 : datagram_service = 0x08
124 : };
125 :
126 : /** Combine two reverse_flags. */
127 : inline reverse_flags
128 9 : operator|(reverse_flags a, reverse_flags b) noexcept
129 : {
130 : return static_cast<reverse_flags>(
131 9 : static_cast<unsigned int>(a) | static_cast<unsigned int>(b));
132 : }
133 :
134 : /** Combine two reverse_flags. */
135 : inline reverse_flags&
136 1 : operator|=(reverse_flags& a, reverse_flags b) noexcept
137 : {
138 1 : a = a | b;
139 1 : return a;
140 : }
141 :
142 : /** Intersect two reverse_flags. */
143 : inline reverse_flags
144 79 : operator&(reverse_flags a, reverse_flags b) noexcept
145 : {
146 : return static_cast<reverse_flags>(
147 79 : static_cast<unsigned int>(a) & static_cast<unsigned int>(b));
148 : }
149 :
150 : /** Intersect two reverse_flags. */
151 : inline reverse_flags&
152 1 : operator&=(reverse_flags& a, reverse_flags b) noexcept
153 : {
154 1 : a = a & b;
155 1 : return a;
156 : }
157 :
158 : /** An asynchronous DNS resolver for coroutine I/O.
159 :
160 : This class provides asynchronous DNS resolution operations that return
161 : awaitable types. Each operation participates in the affine awaitable
162 : protocol, ensuring coroutines resume on the correct executor.
163 :
164 : @par Thread Safety
165 : Distinct objects: Safe.@n
166 : Shared objects: Unsafe. A resolver must not have concurrent resolve
167 : operations.
168 :
169 : @par Semantics
170 : Wraps platform DNS resolution (getaddrinfo/getnameinfo).
171 : Operations dispatch to OS resolver APIs via the io_context
172 : thread pool.
173 :
174 : @par Example
175 : @par !example resolver
176 : */
177 : class BOOST_COROSIO_DECL resolver : public io_object
178 : {
179 : struct resolve_awaitable
180 : : detail::value_op_base<resolve_awaitable, resolver_results>
181 : {
182 : resolver& r_;
183 : std::string host_;
184 : std::string service_;
185 : resolve_flags flags_;
186 :
187 30 : resolve_awaitable(
188 : resolver& r,
189 : std::string_view host,
190 : std::string_view service,
191 : resolve_flags flags) noexcept
192 60 : : r_(r)
193 60 : , host_(host)
194 60 : , service_(service)
195 30 : , flags_(flags)
196 : {
197 30 : }
198 :
199 : std::coroutine_handle<>
200 30 : dispatch(std::coroutine_handle<> h, capy::executor_ref ex) const
201 : {
202 90 : return r_.get().resolve(
203 90 : h, ex, host_, service_, flags_, token_, &ec_, &value_);
204 : }
205 : };
206 :
207 : struct reverse_resolve_awaitable
208 : : detail::
209 : value_op_base<reverse_resolve_awaitable, reverse_resolver_result>
210 : {
211 : resolver& r_;
212 : endpoint ep_;
213 : reverse_flags flags_;
214 :
215 20 : reverse_resolve_awaitable(
216 : resolver& r, endpoint const& ep, reverse_flags flags) noexcept
217 40 : : r_(r)
218 20 : , ep_(ep)
219 20 : , flags_(flags)
220 : {
221 20 : }
222 :
223 : std::coroutine_handle<>
224 20 : dispatch(std::coroutine_handle<> h, capy::executor_ref ex) const
225 : {
226 40 : return r_.get().reverse_resolve(
227 40 : h, ex, ep_, flags_, token_, &ec_, &value_);
228 : }
229 : };
230 :
231 : public:
232 : /** Destructor.
233 :
234 : Cancels any pending operations.
235 : */
236 : ~resolver() override;
237 :
238 : /** Construct a resolver from an execution context.
239 :
240 : @param ctx The execution context that will own this resolver.
241 : */
242 : explicit resolver(capy::execution_context& ctx);
243 :
244 : /** Construct a resolver from an executor.
245 :
246 : The resolver is associated with the executor's context.
247 :
248 : @param ex The executor whose context will own the resolver.
249 : */
250 : template<class Ex>
251 : requires(!std::same_as<std::remove_cvref_t<Ex>, resolver>) &&
252 : capy::Executor<Ex>
253 1 : explicit resolver(Ex const& ex) : resolver(ex.context())
254 : {
255 1 : }
256 :
257 : /** Move constructor.
258 :
259 : Transfers ownership of the resolver resources. After the move,
260 : @p other is in a moved-from state and may only be destroyed or
261 : assigned to.
262 :
263 : @param other The resolver to move from.
264 :
265 : @pre No awaitables returned by @p other's `resolve` methods
266 : exist.
267 : @pre The execution context associated with @p other must
268 : outlive this resolver.
269 : */
270 2 : resolver(resolver&& other) noexcept : io_object(std::move(other)) {}
271 :
272 : /** Move assignment operator.
273 :
274 : Destroys the current implementation and transfers ownership
275 : from @p other. After the move, @p other is in a moved-from
276 : state and may only be destroyed or assigned to.
277 :
278 : @param other The resolver to move from.
279 :
280 : @pre No awaitables returned by either `*this` or @p other's
281 : `resolve` methods exist.
282 : @pre The execution context associated with @p other must
283 : outlive this resolver.
284 :
285 : @return Reference to this resolver.
286 : */
287 2 : resolver& operator=(resolver&& other) noexcept
288 : {
289 2 : if (this != &other)
290 2 : h_ = std::move(other.h_);
291 2 : return *this;
292 : }
293 :
294 : resolver(resolver const&) = delete;
295 : resolver& operator=(resolver const&) = delete;
296 :
297 : /** Initiate an asynchronous resolve operation.
298 :
299 : Resolves the host and service names into a list of endpoints.
300 :
301 : This resolver must outlive the returned awaitable.
302 :
303 : @param host A string identifying a location. May be a descriptive
304 : name or a numeric address string.
305 :
306 : @param service A string identifying the requested service. This may
307 : be a descriptive name or a numeric string corresponding to a
308 : port number.
309 :
310 : @return An awaitable that completes with `io_result<resolver_results>`.
311 :
312 : @note `resolver_results` is an alias for `std::vector<resolver_entry>`.
313 : Copying it deep-copies every entry (each owns two `std::string`s);
314 : move it (`std::move(results)`) or pass iterators when handing it to
315 : a by-value sink such as @ref connect.
316 :
317 : @par Example
318 : @par !example forward_resolve
319 : */
320 14 : [[nodiscard]] auto resolve(std::string_view host, std::string_view service)
321 : {
322 14 : return resolve_awaitable(*this, host, service, resolve_flags::none);
323 : }
324 :
325 : /** Initiate an asynchronous resolve operation with flags.
326 :
327 : Resolves the host and service names into a list of endpoints.
328 :
329 : This resolver must outlive the returned awaitable.
330 :
331 : @param host A string identifying a location.
332 :
333 : @param service A string identifying the requested service.
334 :
335 : @param flags Flags controlling resolution behavior.
336 :
337 : @return An awaitable that completes with `io_result<resolver_results>`.
338 : */
339 16 : [[nodiscard]] auto resolve(
340 : std::string_view host, std::string_view service, resolve_flags flags)
341 : {
342 16 : return resolve_awaitable(*this, host, service, flags);
343 : }
344 :
345 : /** Initiate an asynchronous reverse resolve operation.
346 :
347 : Resolves an endpoint into a hostname and service name using
348 : reverse DNS lookup (PTR record query).
349 :
350 : This resolver must outlive the returned awaitable.
351 :
352 : @param ep The endpoint to resolve.
353 :
354 : @return An awaitable that completes with
355 : `io_result<reverse_resolver_result>`.
356 :
357 : @par Example
358 : @par !example reverse_resolve
359 : */
360 11 : [[nodiscard]] auto resolve(endpoint const& ep)
361 : {
362 11 : return reverse_resolve_awaitable(*this, ep, reverse_flags::none);
363 : }
364 :
365 : /** Initiate an asynchronous reverse resolve operation with flags.
366 :
367 : Resolves an endpoint into a hostname and service name using
368 : reverse DNS lookup (PTR record query).
369 :
370 : This resolver must outlive the returned awaitable.
371 :
372 : @param ep The endpoint to resolve.
373 :
374 : @param flags Flags controlling resolution behavior. See reverse_flags.
375 :
376 : @return An awaitable that completes with
377 : `io_result<reverse_resolver_result>`.
378 : */
379 9 : [[nodiscard]] auto resolve(endpoint const& ep, reverse_flags flags)
380 : {
381 9 : return reverse_resolve_awaitable(*this, ep, flags);
382 : }
383 :
384 : /** Cancel any pending asynchronous operations.
385 :
386 : All outstanding operations complete with `errc::operation_canceled`.
387 : Check `ec == cond::canceled` for portable comparison.
388 : */
389 : void cancel() noexcept;
390 :
391 : public:
392 : /** Backend interface for DNS resolution operations.
393 :
394 : Platform backends derive from this to implement forward and
395 : reverse DNS resolution via getaddrinfo/getnameinfo.
396 : */
397 : struct implementation : io_object::implementation
398 : {
399 : /// Initiate an asynchronous forward DNS resolution.
400 : virtual std::coroutine_handle<> resolve(
401 : std::coroutine_handle<>,
402 : capy::executor_ref,
403 : std::string_view host,
404 : std::string_view service,
405 : resolve_flags flags,
406 : std::stop_token,
407 : std::error_code*,
408 : resolver_results*) = 0;
409 :
410 : /// Initiate an asynchronous reverse DNS resolution.
411 : virtual std::coroutine_handle<> reverse_resolve(
412 : std::coroutine_handle<>,
413 : capy::executor_ref,
414 : endpoint const& ep,
415 : reverse_flags flags,
416 : std::stop_token,
417 : std::error_code*,
418 : reverse_resolver_result*) = 0;
419 :
420 : /// Cancel pending resolve operations.
421 : virtual void cancel() noexcept = 0;
422 : };
423 :
424 : protected:
425 : explicit resolver(handle h) noexcept : io_object(std::move(h)) {}
426 :
427 : private:
428 57 : inline implementation& get() const noexcept
429 : {
430 57 : return *static_cast<implementation*>(h_.get());
431 : }
432 : };
433 :
434 : } // namespace boost::corosio
435 :
436 : #endif
|