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