include/boost/corosio/resolver.hpp

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