include/boost/corosio/resolver.hpp

97.2% Lines (69/71) 100.0% Functions (24/24)
include/boost/corosio/resolver.hpp
Line 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 10 operator|(resolve_flags a, resolve_flags b) noexcept
73 {
74 return static_cast<resolve_flags>(
75 10 static_cast<unsigned int>(a) | static_cast<unsigned int>(b));
76 }
77
78 /** Combine two resolve_flags. */
79 inline resolve_flags&
80 1 operator|=(resolve_flags& a, resolve_flags b) noexcept
81 {
82 1 a = a | b;
83 1 return a;
84 }
85
86 /** Intersect two resolve_flags. */
87 inline resolve_flags
88 103 operator&(resolve_flags a, resolve_flags b) noexcept
89 {
90 return static_cast<resolve_flags>(
91 103 static_cast<unsigned int>(a) & static_cast<unsigned int>(b));
92 }
93
94 /** Intersect two resolve_flags. */
95 inline resolve_flags&
96 1 operator&=(resolve_flags& a, resolve_flags b) noexcept
97 {
98 1 a = a & b;
99 1 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 6 operator|(reverse_flags a, reverse_flags b) noexcept
127 {
128 return static_cast<reverse_flags>(
129 6 static_cast<unsigned int>(a) | static_cast<unsigned int>(b));
130 }
131
132 /** Combine two reverse_flags. */
133 inline reverse_flags&
134 1 operator|=(reverse_flags& a, reverse_flags b) noexcept
135 {
136 1 a = a | b;
137 1 return a;
138 }
139
140 /** Intersect two reverse_flags. */
141 inline reverse_flags
142 47 operator&(reverse_flags a, reverse_flags b) noexcept
143 {
144 return static_cast<reverse_flags>(
145 47 static_cast<unsigned int>(a) & static_cast<unsigned int>(b));
146 }
147
148 /** Intersect two reverse_flags. */
149 inline reverse_flags&
150 1 operator&=(reverse_flags& a, reverse_flags b) noexcept
151 {
152 1 a = a & b;
153 1 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 16 resolve_awaitable(
202 resolver& r,
203 std::string_view host,
204 std::string_view service,
205 resolve_flags flags) noexcept
206 16 : r_(r)
207 32 , host_(host)
208 32 , service_(service)
209 16 , flags_(flags)
210 {
211 16 }
212
213 16 bool await_ready() const noexcept
214 {
215 16 return token_.stop_requested();
216 }
217
218 16 capy::io_result<resolver_results> await_resume() const noexcept
219 {
220 16 if (token_.stop_requested())
221 return {make_error_code(std::errc::operation_canceled), {}};
222 16 return {ec_, std::move(results_)};
223 16 }
224
225 16 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
226 -> std::coroutine_handle<>
227 {
228 16 token_ = env->stop_token;
229 48 return r_.get().resolve(
230 16 h, env->executor, host_, service_, flags_, token_, &ec_,
231 32 &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 10 reverse_resolve_awaitable(
245 resolver& r, endpoint const& ep, reverse_flags flags) noexcept
246 10 : r_(r)
247 10 , ep_(ep)
248 10 , flags_(flags)
249 {
250 10 }
251
252 10 bool await_ready() const noexcept
253 {
254 10 return token_.stop_requested();
255 }
256
257 10 capy::io_result<reverse_resolver_result> await_resume() const noexcept
258 {
259 10 if (token_.stop_requested())
260 return {make_error_code(std::errc::operation_canceled), {}};
261 10 return {ec_, std::move(result_)};
262 10 }
263
264 10 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
265 -> std::coroutine_handle<>
266 {
267 10 token_ = env->stop_token;
268 20 return r_.get().reverse_resolve(
269 20 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 1 explicit resolver(Ex const& ex) : resolver(ex.context())
296 {
297 1 }
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 1 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 2 resolver& operator=(resolver&& other) noexcept
330 {
331 2 if (this != &other)
332 2 h_ = std::move(other.h_);
333 2 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 5 auto resolve(std::string_view host, std::string_view service)
360 {
361 5 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 11 auto resolve(
379 std::string_view host, std::string_view service, resolve_flags flags)
380 {
381 11 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 3 auto resolve(endpoint const& ep)
405 {
406 3 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 7 auto resolve(endpoint const& ep, reverse_flags flags)
424 {
425 7 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 30 inline implementation& get() const noexcept
465 {
466 30 return *static_cast<implementation*>(h_.get());
467 }
468 };
469
470 } // namespace boost::corosio
471
472 #endif
473