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