1  
//
1  
//
2  
// Copyright (c) 2026 Steve Gerbino
2  
// Copyright (c) 2026 Steve Gerbino
3  
//
3  
//
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
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)
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
//
6  
//
7  
// Official repository: https://github.com/cppalliance/corosio
7  
// Official repository: https://github.com/cppalliance/corosio
8  
//
8  
//
9  

9  

10  
#ifndef BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_HPP
10  
#ifndef BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_HPP
11  
#define BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_HPP
11  
#define BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_HPP
12  

12  

13  
#include <boost/corosio/detail/platform.hpp>
13  
#include <boost/corosio/detail/platform.hpp>
14  

14  

15  
#if BOOST_COROSIO_POSIX
15  
#if BOOST_COROSIO_POSIX
16  

16  

17  
#include <boost/corosio/detail/config.hpp>
17  
#include <boost/corosio/detail/config.hpp>
18  
#include <boost/corosio/resolver.hpp>
18  
#include <boost/corosio/resolver.hpp>
19  
#include <boost/capy/ex/execution_context.hpp>
19  
#include <boost/capy/ex/execution_context.hpp>
20  

20  

21  
#include <boost/corosio/native/detail/endpoint_convert.hpp>
21  
#include <boost/corosio/native/detail/endpoint_convert.hpp>
22  
#include <boost/corosio/detail/intrusive.hpp>
22  
#include <boost/corosio/detail/intrusive.hpp>
23  
#include <boost/corosio/detail/dispatch_coro.hpp>
23  
#include <boost/corosio/detail/dispatch_coro.hpp>
24  
#include <boost/corosio/detail/scheduler_op.hpp>
24  
#include <boost/corosio/detail/scheduler_op.hpp>
25  
#include <boost/corosio/detail/thread_pool.hpp>
25  
#include <boost/corosio/detail/thread_pool.hpp>
26  

26  

27  
#include <boost/corosio/detail/scheduler.hpp>
27  
#include <boost/corosio/detail/scheduler.hpp>
28  
#include <boost/corosio/resolver_results.hpp>
28  
#include <boost/corosio/resolver_results.hpp>
29  
#include <boost/capy/ex/executor_ref.hpp>
29  
#include <boost/capy/ex/executor_ref.hpp>
30  
#include <coroutine>
30  
#include <coroutine>
31  
#include <boost/capy/error.hpp>
31  
#include <boost/capy/error.hpp>
32  

32  

33  
#include <netdb.h>
33  
#include <netdb.h>
34  
#include <netinet/in.h>
34  
#include <netinet/in.h>
35  
#include <sys/socket.h>
35  
#include <sys/socket.h>
36  

36  

37  
#include <atomic>
37  
#include <atomic>
38  
#include <memory>
38  
#include <memory>
39  
#include <optional>
39  
#include <optional>
40  
#include <stop_token>
40  
#include <stop_token>
41  
#include <string>
41  
#include <string>
42  

42  

43  
/*
43  
/*
44  
    POSIX Resolver Service
44  
    POSIX Resolver Service
45  
    ======================
45  
    ======================
46  

46  

47  
    POSIX getaddrinfo() is a blocking call that cannot be monitored with
47  
    POSIX getaddrinfo() is a blocking call that cannot be monitored with
48  
    epoll/kqueue/io_uring. Blocking calls are dispatched to a shared
48  
    epoll/kqueue/io_uring. Blocking calls are dispatched to a shared
49  
    resolver_thread_pool service which reuses threads across operations.
49  
    resolver_thread_pool service which reuses threads across operations.
50  

50  

51  
    Cancellation
51  
    Cancellation
52  
    ------------
52  
    ------------
53  
    getaddrinfo() cannot be interrupted mid-call. We use an atomic flag to
53  
    getaddrinfo() cannot be interrupted mid-call. We use an atomic flag to
54  
    indicate cancellation was requested. The worker thread checks this flag
54  
    indicate cancellation was requested. The worker thread checks this flag
55  
    after getaddrinfo() returns and reports the appropriate error.
55  
    after getaddrinfo() returns and reports the appropriate error.
56  

56  

57  
    Class Hierarchy
57  
    Class Hierarchy
58  
    ---------------
58  
    ---------------
59  
    - posix_resolver_service (execution_context service, one per context)
59  
    - posix_resolver_service (execution_context service, one per context)
60  
        - Owns all posix_resolver instances via shared_ptr
60  
        - Owns all posix_resolver instances via shared_ptr
61  
        - Stores scheduler* for posting completions
61  
        - Stores scheduler* for posting completions
62  
    - posix_resolver (one per resolver object)
62  
    - posix_resolver (one per resolver object)
63  
        - Contains embedded resolve_op and reverse_resolve_op for reuse
63  
        - Contains embedded resolve_op and reverse_resolve_op for reuse
64  
        - Uses shared_from_this to prevent premature destruction
64  
        - Uses shared_from_this to prevent premature destruction
65  
    - resolve_op (forward resolution state)
65  
    - resolve_op (forward resolution state)
66  
        - Uses getaddrinfo() to resolve host/service to endpoints
66  
        - Uses getaddrinfo() to resolve host/service to endpoints
67  
    - reverse_resolve_op (reverse resolution state)
67  
    - reverse_resolve_op (reverse resolution state)
68  
        - Uses getnameinfo() to resolve endpoint to host/service
68  
        - Uses getnameinfo() to resolve endpoint to host/service
69  

69  

70  
    Completion Flow
70  
    Completion Flow
71  
    ---------------
71  
    ---------------
72  
    Forward resolution:
72  
    Forward resolution:
73  
    1. resolve() sets up op_, posts work to the thread pool
73  
    1. resolve() sets up op_, posts work to the thread pool
74  
    2. Pool thread runs getaddrinfo() (blocking)
74  
    2. Pool thread runs getaddrinfo() (blocking)
75  
    3. Pool thread stores results in op_.stored_results
75  
    3. Pool thread stores results in op_.stored_results
76  
    4. Pool thread calls svc_.post(&op_) to queue completion
76  
    4. Pool thread calls svc_.post(&op_) to queue completion
77  
    5. Scheduler invokes op_() which resumes the coroutine
77  
    5. Scheduler invokes op_() which resumes the coroutine
78  

78  

79  
    Reverse resolution follows the same pattern using getnameinfo().
79  
    Reverse resolution follows the same pattern using getnameinfo().
80  

80  

81  
    Single-Inflight Constraint
81  
    Single-Inflight Constraint
82  
    --------------------------
82  
    --------------------------
83  
    Each resolver has ONE embedded op_ for forward and ONE reverse_op_ for
83  
    Each resolver has ONE embedded op_ for forward and ONE reverse_op_ for
84  
    reverse resolution. Concurrent operations of the same type on the same
84  
    reverse resolution. Concurrent operations of the same type on the same
85  
    resolver would corrupt state. Users must serialize operations per-resolver.
85  
    resolver would corrupt state. Users must serialize operations per-resolver.
86  

86  

87  
    Shutdown
87  
    Shutdown
88  
    --------
88  
    --------
89  
    The resolver service cancels all resolvers and clears the impl map.
89  
    The resolver service cancels all resolvers and clears the impl map.
90  
    The thread pool service shuts down separately via execution_context
90  
    The thread pool service shuts down separately via execution_context
91  
    service ordering, joining all worker threads.
91  
    service ordering, joining all worker threads.
92  
*/
92  
*/
93  

93  

94  
namespace boost::corosio::detail {
94  
namespace boost::corosio::detail {
95  

95  

96  
struct scheduler;
96  
struct scheduler;
97  

97  

98  
namespace posix_resolver_detail {
98  
namespace posix_resolver_detail {
99  

99  

100  
// Convert resolve_flags to addrinfo ai_flags
100  
// Convert resolve_flags to addrinfo ai_flags
101  
int flags_to_hints(resolve_flags flags);
101  
int flags_to_hints(resolve_flags flags);
102  

102  

103  
// Convert reverse_flags to getnameinfo NI_* flags
103  
// Convert reverse_flags to getnameinfo NI_* flags
104  
int flags_to_ni_flags(reverse_flags flags);
104  
int flags_to_ni_flags(reverse_flags flags);
105  

105  

106  
// Convert addrinfo results to resolver_results
106  
// Convert addrinfo results to resolver_results
107  
resolver_results convert_results(
107  
resolver_results convert_results(
108  
    struct addrinfo* ai, std::string_view host, std::string_view service);
108  
    struct addrinfo* ai, std::string_view host, std::string_view service);
109  

109  

110  
// Convert getaddrinfo error codes to std::error_code
110  
// Convert getaddrinfo error codes to std::error_code
111  
std::error_code make_gai_error(int gai_err);
111  
std::error_code make_gai_error(int gai_err);
112  

112  

113  
} // namespace posix_resolver_detail
113  
} // namespace posix_resolver_detail
114  

114  

115  
class posix_resolver_service;
115  
class posix_resolver_service;
116  

116  

117  
/** Resolver implementation for POSIX backends.
117  
/** Resolver implementation for POSIX backends.
118  

118  

119  
    Each resolver instance contains a single embedded operation object (op_)
119  
    Each resolver instance contains a single embedded operation object (op_)
120  
    that is reused for each resolve() call. This design avoids per-operation
120  
    that is reused for each resolve() call. This design avoids per-operation
121  
    heap allocation but imposes a critical constraint:
121  
    heap allocation but imposes a critical constraint:
122  

122  

123  
    @par Single-Inflight Contract
123  
    @par Single-Inflight Contract
124  

124  

125  
    Only ONE resolve operation may be in progress at a time per resolver
125  
    Only ONE resolve operation may be in progress at a time per resolver
126  
    instance. Calling resolve() while a previous resolve() is still pending
126  
    instance. Calling resolve() while a previous resolve() is still pending
127  
    results in undefined behavior:
127  
    results in undefined behavior:
128  

128  

129  
    - The new call overwrites op_ fields (host, service, coroutine handle)
129  
    - The new call overwrites op_ fields (host, service, coroutine handle)
130  
    - The worker thread from the first call reads corrupted state
130  
    - The worker thread from the first call reads corrupted state
131  
    - The wrong coroutine may be resumed, or resumed multiple times
131  
    - The wrong coroutine may be resumed, or resumed multiple times
132  
    - Data races occur on non-atomic op_ members
132  
    - Data races occur on non-atomic op_ members
133  

133  

134  
    @par Safe Usage Patterns
134  
    @par Safe Usage Patterns
135  

135  

136  
    @code
136  
    @code
137  
    // CORRECT: Sequential resolves
137  
    // CORRECT: Sequential resolves
138  
    auto [ec1, r1] = co_await resolver.resolve("host1", "80");
138  
    auto [ec1, r1] = co_await resolver.resolve("host1", "80");
139  
    auto [ec2, r2] = co_await resolver.resolve("host2", "80");
139  
    auto [ec2, r2] = co_await resolver.resolve("host2", "80");
140  

140  

141  
    // CORRECT: Parallel resolves with separate resolver instances
141  
    // CORRECT: Parallel resolves with separate resolver instances
142  
    resolver r1(ctx), r2(ctx);
142  
    resolver r1(ctx), r2(ctx);
143  
    auto [ec1, res1] = co_await r1.resolve("host1", "80");  // in one coroutine
143  
    auto [ec1, res1] = co_await r1.resolve("host1", "80");  // in one coroutine
144  
    auto [ec2, res2] = co_await r2.resolve("host2", "80");  // in another
144  
    auto [ec2, res2] = co_await r2.resolve("host2", "80");  // in another
145  

145  

146  
    // WRONG: Concurrent resolves on same resolver
146  
    // WRONG: Concurrent resolves on same resolver
147  
    // These may run concurrently if launched in parallel - UNDEFINED BEHAVIOR
147  
    // These may run concurrently if launched in parallel - UNDEFINED BEHAVIOR
148  
    auto f1 = resolver.resolve("host1", "80");
148  
    auto f1 = resolver.resolve("host1", "80");
149  
    auto f2 = resolver.resolve("host2", "80");  // BAD: overlaps with f1
149  
    auto f2 = resolver.resolve("host2", "80");  // BAD: overlaps with f1
150  
    @endcode
150  
    @endcode
151  

151  

152  
    @par Thread Safety
152  
    @par Thread Safety
153  
    Distinct objects: Safe.
153  
    Distinct objects: Safe.
154  
    Shared objects: Unsafe. See single-inflight contract above.
154  
    Shared objects: Unsafe. See single-inflight contract above.
155  
*/
155  
*/
156  
class posix_resolver final
156  
class posix_resolver final
157  
    : public resolver::implementation
157  
    : public resolver::implementation
158  
    , public std::enable_shared_from_this<posix_resolver>
158  
    , public std::enable_shared_from_this<posix_resolver>
159  
    , public intrusive_list<posix_resolver>::node
159  
    , public intrusive_list<posix_resolver>::node
160  
{
160  
{
161  
    friend class posix_resolver_service;
161  
    friend class posix_resolver_service;
162  

162  

163  
public:
163  
public:
164  
    // resolve_op - operation state for a single DNS resolution
164  
    // resolve_op - operation state for a single DNS resolution
165  

165  

166  
    struct resolve_op : scheduler_op
166  
    struct resolve_op : scheduler_op
167  
    {
167  
    {
168  
        struct canceller
168  
        struct canceller
169  
        {
169  
        {
170  
            resolve_op* op;
170  
            resolve_op* op;
171  
            void operator()() const noexcept
171  
            void operator()() const noexcept
172  
            {
172  
            {
173  
                op->request_cancel();
173  
                op->request_cancel();
174  
            }
174  
            }
175  
        };
175  
        };
176  

176  

177  
        // Coroutine state
177  
        // Coroutine state
178  
        std::coroutine_handle<> h;
178  
        std::coroutine_handle<> h;
179  
        detail::continuation_op cont_op;
179  
        detail::continuation_op cont_op;
180  
        capy::executor_ref ex;
180  
        capy::executor_ref ex;
181  
        posix_resolver* impl = nullptr;
181  
        posix_resolver* impl = nullptr;
182  

182  

183  
        // Output parameters
183  
        // Output parameters
184  
        std::error_code* ec_out = nullptr;
184  
        std::error_code* ec_out = nullptr;
185  
        resolver_results* out   = nullptr;
185  
        resolver_results* out   = nullptr;
186  

186  

187  
        // Input parameters (owned copies for thread safety)
187  
        // Input parameters (owned copies for thread safety)
188  
        std::string host;
188  
        std::string host;
189  
        std::string service;
189  
        std::string service;
190  
        resolve_flags flags = resolve_flags::none;
190  
        resolve_flags flags = resolve_flags::none;
191  

191  

192  
        // Result storage (populated by worker thread)
192  
        // Result storage (populated by worker thread)
193  
        resolver_results stored_results;
193  
        resolver_results stored_results;
194  
        int gai_error = 0;
194  
        int gai_error = 0;
195  

195  

196  
        // Thread coordination
196  
        // Thread coordination
197  
        std::atomic<bool> cancelled{false};
197  
        std::atomic<bool> cancelled{false};
198  
        std::optional<std::stop_callback<canceller>> stop_cb;
198  
        std::optional<std::stop_callback<canceller>> stop_cb;
199  

199  

200  
        resolve_op() = default;
200  
        resolve_op() = default;
201  

201  

202  
        void reset() noexcept;
202  
        void reset() noexcept;
203  
        void operator()() override;
203  
        void operator()() override;
204  
        void destroy() override;
204  
        void destroy() override;
205  
        void request_cancel() noexcept;
205  
        void request_cancel() noexcept;
206  
        void start(std::stop_token const& token);
206  
        void start(std::stop_token const& token);
207  
    };
207  
    };
208  

208  

209  
    // reverse_resolve_op - operation state for reverse DNS resolution
209  
    // reverse_resolve_op - operation state for reverse DNS resolution
210  

210  

211  
    struct reverse_resolve_op : scheduler_op
211  
    struct reverse_resolve_op : scheduler_op
212  
    {
212  
    {
213  
        struct canceller
213  
        struct canceller
214  
        {
214  
        {
215  
            reverse_resolve_op* op;
215  
            reverse_resolve_op* op;
216  
            void operator()() const noexcept
216  
            void operator()() const noexcept
217  
            {
217  
            {
218  
                op->request_cancel();
218  
                op->request_cancel();
219  
            }
219  
            }
220  
        };
220  
        };
221  

221  

222  
        // Coroutine state
222  
        // Coroutine state
223  
        std::coroutine_handle<> h;
223  
        std::coroutine_handle<> h;
224  
        detail::continuation_op cont_op;
224  
        detail::continuation_op cont_op;
225  
        capy::executor_ref ex;
225  
        capy::executor_ref ex;
226  
        posix_resolver* impl = nullptr;
226  
        posix_resolver* impl = nullptr;
227  

227  

228  
        // Output parameters
228  
        // Output parameters
229  
        std::error_code* ec_out             = nullptr;
229  
        std::error_code* ec_out             = nullptr;
230  
        reverse_resolver_result* result_out = nullptr;
230  
        reverse_resolver_result* result_out = nullptr;
231  

231  

232  
        // Input parameters
232  
        // Input parameters
233  
        endpoint ep;
233  
        endpoint ep;
234  
        reverse_flags flags = reverse_flags::none;
234  
        reverse_flags flags = reverse_flags::none;
235  

235  

236  
        // Result storage (populated by worker thread)
236  
        // Result storage (populated by worker thread)
237  
        std::string stored_host;
237  
        std::string stored_host;
238  
        std::string stored_service;
238  
        std::string stored_service;
239  
        int gai_error = 0;
239  
        int gai_error = 0;
240  

240  

241  
        // Thread coordination
241  
        // Thread coordination
242  
        std::atomic<bool> cancelled{false};
242  
        std::atomic<bool> cancelled{false};
243  
        std::optional<std::stop_callback<canceller>> stop_cb;
243  
        std::optional<std::stop_callback<canceller>> stop_cb;
244  

244  

245  
        reverse_resolve_op() = default;
245  
        reverse_resolve_op() = default;
246  

246  

247  
        void reset() noexcept;
247  
        void reset() noexcept;
248  
        void operator()() override;
248  
        void operator()() override;
249  
        void destroy() override;
249  
        void destroy() override;
250  
        void request_cancel() noexcept;
250  
        void request_cancel() noexcept;
251  
        void start(std::stop_token const& token);
251  
        void start(std::stop_token const& token);
252  
    };
252  
    };
253  

253  

254  
    /// Embedded pool work item for thread pool dispatch.
254  
    /// Embedded pool work item for thread pool dispatch.
255  
    struct pool_op : pool_work_item
255  
    struct pool_op : pool_work_item
256  
    {
256  
    {
257  
        /// Resolver that owns this work item.
257  
        /// Resolver that owns this work item.
258  
        posix_resolver* resolver_ = nullptr;
258  
        posix_resolver* resolver_ = nullptr;
259  

259  

260  
        /// Prevent impl destruction while work is in flight.
260  
        /// Prevent impl destruction while work is in flight.
261  
        std::shared_ptr<posix_resolver> ref_;
261  
        std::shared_ptr<posix_resolver> ref_;
262  
    };
262  
    };
263  

263  

264  
    explicit posix_resolver(posix_resolver_service& svc) noexcept;
264  
    explicit posix_resolver(posix_resolver_service& svc) noexcept;
265  

265  

266  
    std::coroutine_handle<> resolve(
266  
    std::coroutine_handle<> resolve(
267  
        std::coroutine_handle<>,
267  
        std::coroutine_handle<>,
268  
        capy::executor_ref,
268  
        capy::executor_ref,
269  
        std::string_view host,
269  
        std::string_view host,
270  
        std::string_view service,
270  
        std::string_view service,
271  
        resolve_flags flags,
271  
        resolve_flags flags,
272  
        std::stop_token,
272  
        std::stop_token,
273  
        std::error_code*,
273  
        std::error_code*,
274  
        resolver_results*) override;
274  
        resolver_results*) override;
275  

275  

276  
    std::coroutine_handle<> reverse_resolve(
276  
    std::coroutine_handle<> reverse_resolve(
277  
        std::coroutine_handle<>,
277  
        std::coroutine_handle<>,
278  
        capy::executor_ref,
278  
        capy::executor_ref,
279  
        endpoint const& ep,
279  
        endpoint const& ep,
280  
        reverse_flags flags,
280  
        reverse_flags flags,
281  
        std::stop_token,
281  
        std::stop_token,
282  
        std::error_code*,
282  
        std::error_code*,
283  
        reverse_resolver_result*) override;
283  
        reverse_resolver_result*) override;
284  

284  

285  
    void cancel() noexcept override;
285  
    void cancel() noexcept override;
286  

286  

287  
    resolve_op op_;
287  
    resolve_op op_;
288  
    reverse_resolve_op reverse_op_;
288  
    reverse_resolve_op reverse_op_;
289  

289  

290  
    /// Pool work item for forward resolution.
290  
    /// Pool work item for forward resolution.
291  
    pool_op resolve_pool_op_;
291  
    pool_op resolve_pool_op_;
292  

292  

293  
    /// Pool work item for reverse resolution.
293  
    /// Pool work item for reverse resolution.
294  
    pool_op reverse_pool_op_;
294  
    pool_op reverse_pool_op_;
295  

295  

296  
    /// Execute blocking `getaddrinfo()` on a pool thread.
296  
    /// Execute blocking `getaddrinfo()` on a pool thread.
297  
    static void do_resolve_work(pool_work_item*) noexcept;
297  
    static void do_resolve_work(pool_work_item*) noexcept;
298  

298  

299  
    /// Execute blocking `getnameinfo()` on a pool thread.
299  
    /// Execute blocking `getnameinfo()` on a pool thread.
300  
    static void do_reverse_resolve_work(pool_work_item*) noexcept;
300  
    static void do_reverse_resolve_work(pool_work_item*) noexcept;
301  

301  

302  
private:
302  
private:
303  
    posix_resolver_service& svc_;
303  
    posix_resolver_service& svc_;
304  
};
304  
};
305  

305  

306  
} // namespace boost::corosio::detail
306  
} // namespace boost::corosio::detail
307  

307  

308  
#endif // BOOST_COROSIO_POSIX
308  
#endif // BOOST_COROSIO_POSIX
309  

309  

310  
#endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_HPP
310  
#endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_HPP