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  
        capy::executor_ref ex;
179  
        capy::executor_ref ex;
180  
        posix_resolver* impl = nullptr;
180  
        posix_resolver* impl = nullptr;
181  

181  

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

185  

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

190  

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

194  

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

198  

199  
        resolve_op() = default;
199  
        resolve_op() = default;
200  

200  

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

207  

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

209  

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

220  

221  
        // Coroutine state
221  
        // Coroutine state
222  
        std::coroutine_handle<> h;
222  
        std::coroutine_handle<> h;
223  
        capy::executor_ref ex;
223  
        capy::executor_ref ex;
224  
        posix_resolver* impl = nullptr;
224  
        posix_resolver* impl = nullptr;
225  

225  

226  
        // Output parameters
226  
        // Output parameters
227  
        std::error_code* ec_out             = nullptr;
227  
        std::error_code* ec_out             = nullptr;
228  
        reverse_resolver_result* result_out = nullptr;
228  
        reverse_resolver_result* result_out = nullptr;
229  

229  

230  
        // Input parameters
230  
        // Input parameters
231  
        endpoint ep;
231  
        endpoint ep;
232  
        reverse_flags flags = reverse_flags::none;
232  
        reverse_flags flags = reverse_flags::none;
233  

233  

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

238  

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

242  

243  
        reverse_resolve_op() = default;
243  
        reverse_resolve_op() = default;
244  

244  

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

251  

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

257  

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

261  

262  
    explicit posix_resolver(posix_resolver_service& svc) noexcept;
262  
    explicit posix_resolver(posix_resolver_service& svc) noexcept;
263  

263  

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

273  

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

282  

283  
    void cancel() noexcept override;
283  
    void cancel() noexcept override;
284  

284  

285  
    resolve_op op_;
285  
    resolve_op op_;
286  
    reverse_resolve_op reverse_op_;
286  
    reverse_resolve_op reverse_op_;
287  

287  

288  
    /// Pool work item for forward resolution.
288  
    /// Pool work item for forward resolution.
289  
    pool_op resolve_pool_op_;
289  
    pool_op resolve_pool_op_;
290  

290  

291  
    /// Pool work item for reverse resolution.
291  
    /// Pool work item for reverse resolution.
292  
    pool_op reverse_pool_op_;
292  
    pool_op reverse_pool_op_;
293  

293  

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

296  

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

299  

300  
private:
300  
private:
301  
    posix_resolver_service& svc_;
301  
    posix_resolver_service& svc_;
302  
};
302  
};
303  

303  

304  
} // namespace boost::corosio::detail
304  
} // namespace boost::corosio::detail
305  

305  

306  
#endif // BOOST_COROSIO_POSIX
306  
#endif // BOOST_COROSIO_POSIX
307  

307  

308  
#endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_HPP
308  
#endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_HPP