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/results.hpp>
20 : #include <boost/capy/ex/executor_ref.hpp>
21 : #include <boost/capy/ex/execution_context.hpp>
22 : #include <boost/capy/ex/await_context.hpp>
23 : #include <boost/capy/concept/executor.hpp>
24 :
25 : #include <system_error>
26 :
27 : #include <cassert>
28 : #include <concepts>
29 : #include <boost/capy/continuation.hpp>
30 :
31 : #include <coroutine>
32 : #include <stop_token>
33 : #include <string>
34 : #include <string_view>
35 : #include <vector>
36 : #include <type_traits>
37 :
38 : namespace boost::corosio {
39 :
40 : /** Bitmask flags for resolver queries.
41 :
42 : These flags correspond to the hints parameter of `getaddrinfo`.
43 : */
44 : enum class resolve_flags : unsigned int
45 : {
46 : /// No flags.
47 : none = 0,
48 :
49 : /// Indicate that returned endpoint is intended for use as a locally
50 : /// bound socket endpoint.
51 : passive = 0x01,
52 :
53 : /// Host name should be treated as a numeric string defining an IPv4
54 : /// or IPv6 address and no name resolution should be attempted.
55 : numeric_host = 0x04,
56 :
57 : /// Service name should be treated as a numeric string defining a port
58 : /// number and no name resolution should be attempted.
59 : numeric_service = 0x08,
60 :
61 : /// Only return IPv4 addresses if a non-loopback IPv4 address is
62 : /// configured for the system. Only return IPv6 addresses if a
63 : /// non-loopback IPv6 address is configured for the system.
64 : address_configured = 0x20,
65 :
66 : /// If the query protocol family is specified as IPv6, return
67 : /// IPv4-mapped IPv6 addresses on finding no IPv6 addresses.
68 : v4_mapped = 0x800,
69 :
70 : /// If used with v4_mapped, return all matching IPv6 and IPv4 addresses.
71 : all_matching = 0x100
72 : };
73 :
74 : /** Combine two `resolve_flags`. */
75 : inline resolve_flags
76 HIT 23 : operator|(resolve_flags a, resolve_flags b) noexcept
77 : {
78 : return static_cast<resolve_flags>(
79 23 : static_cast<unsigned int>(a) | static_cast<unsigned int>(b));
80 : }
81 :
82 : /** Combine two `resolve_flags`. */
83 : inline resolve_flags&
84 1 : operator|=(resolve_flags& a, resolve_flags b) noexcept
85 : {
86 1 : a = a | b;
87 1 : return a;
88 : }
89 :
90 : /** Intersect two `resolve_flags`. */
91 : inline resolve_flags
92 235 : operator&(resolve_flags a, resolve_flags b) noexcept
93 : {
94 : return static_cast<resolve_flags>(
95 235 : static_cast<unsigned int>(a) & static_cast<unsigned int>(b));
96 : }
97 :
98 : /** Intersect two `resolve_flags`. */
99 : inline resolve_flags&
100 1 : operator&=(resolve_flags& a, resolve_flags b) noexcept
101 : {
102 1 : a = a & b;
103 1 : return a;
104 : }
105 :
106 : /** Bitmask flags for reverse resolver queries.
107 :
108 : These flags correspond to the flags parameter of `getnameinfo`.
109 : */
110 : enum class reverse_flags : unsigned int
111 : {
112 : /// No flags.
113 : none = 0,
114 :
115 : /// Return the numeric form of the hostname instead of its name.
116 : numeric_host = 0x01,
117 :
118 : /// Return the numeric form of the service name instead of its name.
119 : numeric_service = 0x02,
120 :
121 : /// Return an error if the hostname cannot be resolved.
122 : name_required = 0x04,
123 :
124 : /// Lookup for datagram (UDP) service instead of stream (TCP).
125 : datagram_service = 0x08
126 : };
127 :
128 : /** Combine two `reverse_flags`. */
129 : inline reverse_flags
130 11 : operator|(reverse_flags a, reverse_flags b) noexcept
131 : {
132 : return static_cast<reverse_flags>(
133 11 : static_cast<unsigned int>(a) | static_cast<unsigned int>(b));
134 : }
135 :
136 : /** Combine two `reverse_flags`. */
137 : inline reverse_flags&
138 1 : operator|=(reverse_flags& a, reverse_flags b) noexcept
139 : {
140 1 : a = a | b;
141 1 : return a;
142 : }
143 :
144 : /** Intersect two `reverse_flags`. */
145 : inline reverse_flags
146 71 : operator&(reverse_flags a, reverse_flags b) noexcept
147 : {
148 : return static_cast<reverse_flags>(
149 71 : static_cast<unsigned int>(a) & static_cast<unsigned int>(b));
150 : }
151 :
152 : /** Intersect two `reverse_flags`. */
153 : inline reverse_flags&
154 1 : operator&=(reverse_flags& a, reverse_flags b) noexcept
155 : {
156 1 : a = a & b;
157 1 : return a;
158 : }
159 :
160 : /** The name of an endpoint.
161 :
162 : Reverse resolution translates an endpoint into its symbolic
163 : spelling: the host name and the service name. Both fields carry
164 : resolved data; the endpoint they name is the one the caller
165 : passed to `resolve`.
166 : */
167 : struct endpoint_name
168 : {
169 : /// The resolved host name.
170 : std::string host_name;
171 :
172 : /// The resolved service name.
173 : std::string service_name;
174 : };
175 :
176 : /** Resolves host names and services to endpoints, from a coroutine.
177 :
178 : This class provides asynchronous DNS resolution operations that return
179 : awaitable types. Each operation participates in the affine awaitable
180 : protocol. It resumes the awaiting coroutine through that coroutine's
181 : executor, not through the executor of the context that owns the
182 : resolver.
183 :
184 : @par Thread Safety
185 : Distinct objects: Safe.@n
186 : Shared objects: Unsafe. A resolver must not have concurrent resolve
187 : operations.
188 :
189 : @par Semantics
190 : Wraps platform DNS resolution (`getaddrinfo`/`getnameinfo`).
191 : Operations dispatch to OS resolver APIs via the `io_context`
192 : thread pool.
193 :
194 : @par Example
195 : @par !example resolver
196 : */
197 : class BOOST_COROSIO_DECL resolver : public io_object
198 : {
199 : struct resolve_awaitable
200 : : detail::value_op_base<resolve_awaitable, std::vector<endpoint>>
201 : {
202 : private:
203 : friend resolver;
204 :
205 37 : resolve_awaitable(
206 : resolver& r,
207 : std::string_view host,
208 : std::string_view service,
209 : resolve_flags flags) noexcept
210 74 : : r_(r)
211 74 : , host_(host)
212 74 : , service_(service)
213 37 : , flags_(flags)
214 : {
215 37 : }
216 :
217 : friend detail::value_op_base<resolve_awaitable, std::vector<endpoint>>;
218 : resolver& r_;
219 : std::string host_;
220 : std::string service_;
221 : resolve_flags flags_;
222 :
223 : std::coroutine_handle<>
224 36 : dispatch(capy::continuation& cont, capy::executor_ref ex) const
225 : {
226 108 : return r_.get().resolve(
227 108 : cont, ex, host_, service_, flags_, token_, &ec_, &value_);
228 : }
229 : };
230 :
231 : struct resolve_host_awaitable
232 : : detail::value_op_base<resolve_host_awaitable, std::vector<endpoint>>
233 : {
234 : private:
235 : friend resolver;
236 :
237 6 : resolve_host_awaitable(
238 : resolver& r, std::string_view host, resolve_flags flags) noexcept
239 12 : : r_(r)
240 12 : , host_(host)
241 6 : , flags_(flags)
242 : {
243 6 : }
244 :
245 : friend detail::
246 : value_op_base<resolve_host_awaitable, std::vector<endpoint>>;
247 : resolver& r_;
248 : std::string host_;
249 : resolve_flags flags_;
250 :
251 : std::coroutine_handle<>
252 5 : dispatch(capy::continuation& cont, capy::executor_ref ex) const
253 : {
254 : // An empty service reaches the system resolver as null,
255 : // which is the host-only query
256 15 : return r_.get().resolve(
257 15 : cont, ex, host_, {}, flags_, token_, &ec_, &value_);
258 : }
259 :
260 : public:
261 : // Shadows the base: the endpoint result is reshaped into
262 : // the honest address list
263 : [[nodiscard]] capy::results<std::vector<ip_address>>
264 6 : await_resume() const
265 : {
266 6 : std::vector<ip_address> addrs;
267 6 : addrs.reserve(value_.size());
268 9 : for (auto const& entry : value_)
269 : {
270 3 : auto a = entry.address();
271 3 : bool duplicate = false;
272 3 : for (auto const& seen : addrs)
273 : {
274 MIS 0 : if (seen == a)
275 : {
276 0 : duplicate = true;
277 0 : break;
278 : }
279 : }
280 : // The same address can come back more than once
281 : // (mixed name sources, repeated records); each
282 : // address is reported once
283 HIT 3 : if (!duplicate)
284 3 : addrs.push_back(a);
285 : }
286 12 : return {ec_, std::move(addrs)};
287 6 : }
288 : };
289 :
290 : struct reverse_resolve_awaitable
291 : : detail::value_op_base<reverse_resolve_awaitable, endpoint_name>
292 : {
293 : private:
294 : friend resolver;
295 :
296 22 : reverse_resolve_awaitable(
297 : resolver& r, endpoint const& ep, reverse_flags flags) noexcept
298 44 : : r_(r)
299 22 : , ep_(ep)
300 22 : , flags_(flags)
301 : {
302 22 : }
303 :
304 : friend detail::value_op_base<reverse_resolve_awaitable, endpoint_name>;
305 :
306 : resolver& r_;
307 : endpoint ep_;
308 : reverse_flags flags_;
309 :
310 : std::coroutine_handle<>
311 21 : dispatch(capy::continuation& cont, capy::executor_ref ex) const
312 : {
313 42 : return r_.get().reverse_resolve(
314 42 : cont, ex, ep_, flags_, token_, &ec_, &value_);
315 : }
316 : };
317 :
318 : public:
319 : /** Destructor.
320 :
321 : Cancels any pending operations.
322 : */
323 : ~resolver() override;
324 :
325 : /** Construct a resolver from an execution context.
326 :
327 : @param ctx The execution context that owns this resolver.
328 : */
329 : explicit resolver(capy::execution_context& ctx);
330 :
331 : /** Construct a resolver from an executor.
332 :
333 : The resolver is associated with the executor's context.
334 :
335 : @param ex The executor whose context owns the resolver.
336 : */
337 : template<class Ex>
338 : requires(!std::same_as<std::remove_cvref_t<Ex>, resolver>) &&
339 : capy::Executor<Ex>
340 1 : explicit resolver(Ex const& ex) : resolver(ex.context())
341 : {
342 1 : }
343 :
344 : /** Move constructor.
345 :
346 : Transfers ownership of the resolver resources. After the move,
347 : @p other is in a moved-from state and may only be destroyed or
348 : assigned to.
349 :
350 : @param other The resolver to move from.
351 :
352 : @pre No awaitables returned by @p other's `resolve` methods
353 : exist.
354 : @pre The execution context associated with @p other must
355 : outlive this resolver.
356 : */
357 10 : resolver(resolver&& other) noexcept : io_object(std::move(other)) {}
358 :
359 : /** Move assignment operator.
360 :
361 : Destroys the current implementation and transfers ownership
362 : from @p other. After the move, @p other is in a moved-from
363 : state and may only be destroyed or assigned to.
364 :
365 : @param other The resolver to move from.
366 :
367 : @pre No awaitables returned by either `*this` or @p other's
368 : `resolve` methods exist.
369 : @pre The execution context associated with @p other must
370 : outlive this resolver.
371 :
372 : @return Reference to this resolver.
373 : */
374 2 : resolver& operator=(resolver&& other) noexcept
375 : {
376 2 : if (this != &other)
377 2 : h_ = std::move(other.h_);
378 2 : return *this;
379 : }
380 :
381 : /// Copy construction is disabled; the handle is uniquely owned.
382 : resolver(resolver const&) = delete;
383 : /// Copy assignment is disabled; the handle is uniquely owned.
384 : resolver& operator=(resolver const&) = delete;
385 :
386 : /** Initiate an asynchronous resolve operation.
387 :
388 : Resolves the host and service names into a list of endpoints.
389 :
390 : This resolver must outlive the returned awaitable.
391 :
392 : @param host A string identifying a location. May be a descriptive
393 : name or a numeric address string.
394 :
395 : @param service A string identifying the requested service. This may
396 : be a descriptive name or a numeric string corresponding to a
397 : port number.
398 :
399 : @return An awaitable that completes with
400 : `results<std::vector<endpoint>>`.
401 :
402 : @par Example
403 : @par !example forward_resolve
404 : */
405 15 : [[nodiscard]] auto resolve(std::string_view host, std::string_view service)
406 : {
407 15 : return resolve_awaitable(*this, host, service, resolve_flags::none);
408 : }
409 :
410 : /** Initiate an asynchronous host-only resolve operation.
411 :
412 : Resolves a host name into its addresses, with no service or
413 : port involved — the query `getaddrinfo` performs with a null
414 : service. Use this when the host and port travel separately,
415 : as they do in most configuration.
416 :
417 : Each address appears once in the result even when the query
418 : reports it more than once, and link-local results keep
419 : their zone.
420 :
421 : @param host The host name or numeric address string.
422 :
423 : @return An awaitable that completes with
424 : `results<std::vector<ip_address>>`.
425 :
426 : @par Example
427 : @par !example host_only_resolve
428 : */
429 3 : [[nodiscard]] auto resolve(std::string_view host)
430 : {
431 3 : return resolve_host_awaitable(*this, host, resolve_flags::none);
432 : }
433 :
434 : /** Initiate an asynchronous host-only resolve operation with flags.
435 :
436 : @param host The host name or numeric address string.
437 : @param flags Resolution behavior flags.
438 :
439 : @return An awaitable that completes with
440 : `results<std::vector<ip_address>>`.
441 : */
442 3 : [[nodiscard]] auto resolve(std::string_view host, resolve_flags flags)
443 : {
444 3 : return resolve_host_awaitable(*this, host, flags);
445 : }
446 :
447 : /** Initiate an asynchronous resolve operation with flags.
448 :
449 : Resolves the host and service names into a list of endpoints.
450 :
451 : This resolver must outlive the returned awaitable.
452 :
453 : @param host A string identifying a location.
454 :
455 : @param service A string identifying the requested service.
456 :
457 : @param flags Flags controlling resolution behavior.
458 :
459 : @return An awaitable that completes with
460 : `results<std::vector<endpoint>>`.
461 : */
462 22 : [[nodiscard]] auto resolve(
463 : std::string_view host, std::string_view service, resolve_flags flags)
464 : {
465 22 : return resolve_awaitable(*this, host, service, flags);
466 : }
467 :
468 : /** Initiate an asynchronous reverse resolve operation.
469 :
470 : Resolves an endpoint into a hostname and service name using
471 : reverse DNS lookup (PTR record query).
472 :
473 : This resolver must outlive the returned awaitable.
474 :
475 : @param ep The endpoint to resolve.
476 :
477 : @return An awaitable that completes with
478 : `results<endpoint_name>`.
479 :
480 : @par Example
481 : @par !example reverse_resolve
482 : */
483 11 : [[nodiscard]] auto resolve(endpoint const& ep)
484 : {
485 11 : return reverse_resolve_awaitable(*this, ep, reverse_flags::none);
486 : }
487 :
488 : /** Initiate an asynchronous reverse resolve operation with flags.
489 :
490 : Resolves an endpoint into a hostname and service name using
491 : reverse DNS lookup (PTR record query).
492 :
493 : This resolver must outlive the returned awaitable.
494 :
495 : @param ep The endpoint to resolve.
496 :
497 : @param flags Flags controlling resolution behavior. See reverse_flags.
498 :
499 : @return An awaitable that completes with
500 : `results<endpoint_name>`.
501 : */
502 11 : [[nodiscard]] auto resolve(endpoint const& ep, reverse_flags flags)
503 : {
504 11 : return reverse_resolve_awaitable(*this, ep, flags);
505 : }
506 :
507 : /** Cancel any pending asynchronous operations.
508 :
509 : A resolve transfers no bytes, so a cancellation always wins. An
510 : operation reports `errc::operation_canceled` even when the lookup
511 : had already completed when the cancellation landed. Check
512 : `ec == cond::canceled` for a portable comparison.
513 : */
514 : void cancel() noexcept;
515 :
516 : public:
517 : /** Define backend hooks for DNS resolution operations.
518 :
519 : Platform backends derive from this to implement forward and
520 : reverse DNS resolution via `getaddrinfo`/`getnameinfo`.
521 : */
522 : struct implementation : io_object::implementation
523 : {
524 : /** Initiate an asynchronous forward DNS resolution.
525 :
526 : @param cont Continuation to resume on completion; it lives in
527 : the awaiting frame until then.
528 : @param ex Executor for dispatching the completion.
529 : @param host The host name or address literal to resolve.
530 : @param service The service name or port number.
531 : @param flags Flags controlling the lookup.
532 : @param token Stop token for cancellation.
533 : @param ec Output error code.
534 : @param results Output resolver results.
535 :
536 : @return Coroutine handle to resume immediately.
537 : */
538 : virtual std::coroutine_handle<> resolve(
539 : capy::continuation& cont,
540 : capy::executor_ref ex,
541 : std::string_view host,
542 : std::string_view service,
543 : resolve_flags flags,
544 : std::stop_token token,
545 : std::error_code* ec,
546 : std::vector<endpoint>* results) = 0;
547 :
548 : /** Initiate an asynchronous reverse DNS resolution.
549 :
550 : @param cont Continuation to resume on completion; it lives in
551 : the awaiting frame until then.
552 : @param ex Executor for dispatching the completion.
553 : @param ep The endpoint to resolve.
554 : @param flags Flags controlling the lookup.
555 : @param token Stop token for cancellation.
556 : @param ec Output error code.
557 : @param result Output reverse-resolution result.
558 :
559 : @return Coroutine handle to resume immediately.
560 : */
561 : virtual std::coroutine_handle<> reverse_resolve(
562 : capy::continuation& cont,
563 : capy::executor_ref ex,
564 : endpoint const& ep,
565 : reverse_flags flags,
566 : std::stop_token token,
567 : std::error_code* ec,
568 : endpoint_name* result) = 0;
569 :
570 : /// Cancel pending resolve operations.
571 : virtual void cancel() noexcept = 0;
572 : };
573 :
574 : protected:
575 : /** Adopt an existing handle.
576 :
577 : @param h The handle the resolver takes ownership of.
578 : */
579 : explicit resolver(handle h) noexcept : io_object(std::move(h)) {}
580 :
581 : private:
582 69 : inline implementation& get() const noexcept
583 : {
584 69 : return *static_cast<implementation*>(h_.get());
585 : }
586 : };
587 :
588 : } // namespace boost::corosio
589 :
590 : #endif
|