TLA Line data 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 HIT 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 MIS 0 : return {make_error_code(std::errc::operation_canceled), {}};
222 HIT 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 MIS 0 : return {make_error_code(std::errc::operation_canceled), {}};
261 HIT 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
|