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