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