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.
302
303 @param other The resolver to move from.
304 */
305 1 resolver(resolver&& other) noexcept : io_object(std::move(other)) {}
306
307 /** Move assignment operator.
308
309 Cancels any existing operations and transfers ownership.
310 The source and destination must share the same execution context.
311
312 @param other The resolver to move from.
313
314 @return Reference to this resolver.
315 */
316 2 resolver& operator=(resolver&& other) noexcept
317 {
318 2 if (this != &other)
319 2 h_ = std::move(other.h_);
320 2 return *this;
321 }
322
323 resolver(resolver const&) = delete;
324 resolver& operator=(resolver const&) = delete;
325
326 /** Initiate an asynchronous resolve operation.
327
328 Resolves the host and service names into a list of endpoints.
329
330 @param host A string identifying a location. May be a descriptive
331 name or a numeric address string.
332
333 @param service A string identifying the requested service. This may
334 be a descriptive name or a numeric string corresponding to a
335 port number.
336
337 @return An awaitable that completes with `io_result<resolver_results>`.
338
339 @par Example
340 @code
341 auto [ec, results] = co_await r.resolve("www.example.com", "https");
342 @endcode
343 */
344 5 auto resolve(std::string_view host, std::string_view service)
345 {
346 5 return resolve_awaitable(*this, host, service, resolve_flags::none);
347 }
348
349 /** Initiate an asynchronous resolve operation with flags.
350
351 Resolves the host and service names into a list of endpoints.
352
353 @param host A string identifying a location.
354
355 @param service A string identifying the requested service.
356
357 @param flags Flags controlling resolution behavior.
358
359 @return An awaitable that completes with `io_result<resolver_results>`.
360 */
361 11 auto resolve(
362 std::string_view host, std::string_view service, resolve_flags flags)
363 {
364 11 return resolve_awaitable(*this, host, service, flags);
365 }
366
367 /** Initiate an asynchronous reverse resolve operation.
368
369 Resolves an endpoint into a hostname and service name using
370 reverse DNS lookup (PTR record query).
371
372 @param ep The endpoint to resolve.
373
374 @return An awaitable that completes with
375 `io_result<reverse_resolver_result>`.
376
377 @par Example
378 @code
379 endpoint ep(ipv4_address({127, 0, 0, 1}), 80);
380 auto [ec, result] = co_await r.resolve(ep);
381 if (!ec)
382 std::cout << result.host_name() << ":" << result.service_name();
383 @endcode
384 */
385 3 auto resolve(endpoint const& ep)
386 {
387 3 return reverse_resolve_awaitable(*this, ep, reverse_flags::none);
388 }
389
390 /** Initiate an asynchronous reverse resolve operation with flags.
391
392 Resolves an endpoint into a hostname and service name using
393 reverse DNS lookup (PTR record query).
394
395 @param ep The endpoint to resolve.
396
397 @param flags Flags controlling resolution behavior. See reverse_flags.
398
399 @return An awaitable that completes with
400 `io_result<reverse_resolver_result>`.
401 */
402 7 auto resolve(endpoint const& ep, reverse_flags flags)
403 {
404 7 return reverse_resolve_awaitable(*this, ep, flags);
405 }
406
407 /** Cancel any pending asynchronous operations.
408
409 All outstanding operations complete with `errc::operation_canceled`.
410 Check `ec == cond::canceled` for portable comparison.
411 */
412 void cancel();
413
414 public:
415 struct implementation : io_object::implementation
416 {
417 virtual std::coroutine_handle<> resolve(
418 std::coroutine_handle<>,
419 capy::executor_ref,
420 std::string_view host,
421 std::string_view service,
422 resolve_flags flags,
423 std::stop_token,
424 std::error_code*,
425 resolver_results*) = 0;
426
427 virtual std::coroutine_handle<> reverse_resolve(
428 std::coroutine_handle<>,
429 capy::executor_ref,
430 endpoint const& ep,
431 reverse_flags flags,
432 std::stop_token,
433 std::error_code*,
434 reverse_resolver_result*) = 0;
435
436 virtual void cancel() noexcept = 0;
437 };
438
439 protected:
440 explicit resolver(handle h) noexcept : io_object(std::move(h)) {}
441
442 private:
443 30 inline implementation& get() const noexcept
444 {
445 30 return *static_cast<implementation*>(h_.get());
446 }
447 };
448
449 } // namespace boost::corosio
450
451 #endif
452