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