include/boost/corosio/resolver.hpp

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