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) :205 37x 100.0% 100.0% boost::corosio::resolver::resolve_awaitable::dispatch(boost::capy::continuation&, boost::capy::executor_ref) const :224 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) :237 6x 100.0% 100.0% boost::corosio::resolver::resolve_host_awaitable::dispatch(boost::capy::continuation&, boost::capy::executor_ref) const :252 5x 100.0% 77.0% boost::corosio::resolver::resolve_host_awaitable::await_resume() const :264 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) :296 22x 100.0% 100.0% boost::corosio::resolver::reverse_resolve_awaitable::dispatch(boost::capy::continuation&, boost::capy::executor_ref) const :311 21x 100.0% 80.0% boost::corosio::resolver::resolver<boost::corosio::io_context::executor_type>(boost::corosio::io_context::executor_type const&) :340 1x 100.0% 100.0% boost::corosio::resolver::resolver(boost::corosio::resolver&&) :357 10x 100.0% 100.0% boost::corosio::resolver::operator=(boost::corosio::resolver&&) :374 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> >) :405 15x 100.0% 100.0% boost::corosio::resolver::resolve(std::basic_string_view<char, std::char_traits<char> >) :429 3x 100.0% 100.0% boost::corosio::resolver::resolve(std::basic_string_view<char, std::char_traits<char> >, boost::corosio::resolve_flags) :442 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) :462 22x 100.0% 100.0% boost::corosio::resolver::resolve(boost::corosio::endpoint const&) :483 11x 100.0% 100.0% boost::corosio::resolver::resolve(boost::corosio::endpoint const&, boost::corosio::reverse_flags) :502 11x 100.0% 100.0% boost::corosio::resolver::get() const :582 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. 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 37x resolve_awaitable(
206 resolver& r,
207 std::string_view host,
208 std::string_view service,
209 resolve_flags flags) noexcept
210 74x : r_(r)
211 74x , host_(host)
212 74x , service_(service)
213 37x , flags_(flags)
214 {
215 37x }
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 36x dispatch(capy::continuation& cont, capy::executor_ref ex) const
225 {
226 108x return r_.get().resolve(
227 108x 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 6x resolve_host_awaitable(
238 resolver& r, std::string_view host, resolve_flags flags) noexcept
239 12x : r_(r)
240 12x , host_(host)
241 6x , flags_(flags)
242 {
243 6x }
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 5x 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 15x return r_.get().resolve(
257 15x 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 6x await_resume() const
265 {
266 6x std::vector<ip_address> addrs;
267 6x addrs.reserve(value_.size());
268 9x for (auto const& entry : value_)
269 {
270 3x auto a = entry.address();
271 3x bool duplicate = false;
272 3x for (auto const& seen : addrs)
273 {
274 ✗ if (seen == a)
275 {
276 ✗ duplicate = true;
277 ✗ 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 3x if (!duplicate)
284 3x addrs.push_back(a);
285 }
286 12x return {ec_, std::move(addrs)};
287 6x }
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 22x reverse_resolve_awaitable(
297 resolver& r, endpoint const& ep, reverse_flags flags) noexcept
298 44x : r_(r)
299 22x , ep_(ep)
300 22x , flags_(flags)
301 {
302 22x }
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 21x dispatch(capy::continuation& cont, capy::executor_ref ex) const
312 {
313 42x return r_.get().reverse_resolve(
314 42x 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 1x explicit resolver(Ex const& ex) : resolver(ex.context())
341 {
342 1x }
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 10x 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 2x resolver& operator=(resolver&& other) noexcept
375 {
376 2x if (this != &other)
377 2x h_ = std::move(other.h_);
378 2x 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 15x [[nodiscard]] auto resolve(std::string_view host, std::string_view service)
406 {
407 15x 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 3x [[nodiscard]] auto resolve(std::string_view host)
430 {
431 3x 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 3x [[nodiscard]] auto resolve(std::string_view host, resolve_flags flags)
443 {
444 3x 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 22x [[nodiscard]] auto resolve(
463 std::string_view host, std::string_view service, resolve_flags flags)
464 {
465 22x 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 11x [[nodiscard]] auto resolve(endpoint const& ep)
484 {
485 11x 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 11x [[nodiscard]] auto resolve(endpoint const& ep, reverse_flags flags)
503 {
504 11x 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 69x inline implementation& get() const noexcept
583 {
584 69x return *static_cast<implementation*>(h_.get());
585 }
586 };
587
588 } // namespace boost::corosio
589
590 #endif
591