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/detail/endpoint_convert.hpp>
21  
#include <boost/corosio/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  

25  

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

31  

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

35  

36  
#include <atomic>
36  
#include <atomic>
37  
#include <cassert>
37  
#include <cassert>
38  
#include <condition_variable>
38  
#include <condition_variable>
39  
#include <cstring>
39  
#include <cstring>
40  
#include <memory>
40  
#include <memory>
41  
#include <mutex>
41  
#include <mutex>
42  
#include <optional>
42  
#include <optional>
43  
#include <stop_token>
43  
#include <stop_token>
44  
#include <string>
44  
#include <string>
45  
#include <thread>
45  
#include <thread>
46  
#include <unordered_map>
46  
#include <unordered_map>
47  
#include <vector>
47  
#include <vector>
48  

48  

49  
/*
49  
/*
50  
    POSIX Resolver Service
50  
    POSIX Resolver Service
51  
    ======================
51  
    ======================
52  

52  

53  
    POSIX getaddrinfo() is a blocking call that cannot be monitored with
53  
    POSIX getaddrinfo() is a blocking call that cannot be monitored with
54  
    epoll/kqueue/io_uring. We use a worker thread approach: each resolution
54  
    epoll/kqueue/io_uring. We use a worker thread approach: each resolution
55  
    spawns a dedicated thread that runs the blocking call and posts completion
55  
    spawns a dedicated thread that runs the blocking call and posts completion
56  
    back to the scheduler.
56  
    back to the scheduler.
57  

57  

58  
    Thread-per-resolution Design
58  
    Thread-per-resolution Design
59  
    ----------------------------
59  
    ----------------------------
60  
    Simple, no thread pool complexity. DNS lookups are infrequent enough that
60  
    Simple, no thread pool complexity. DNS lookups are infrequent enough that
61  
    thread creation overhead is acceptable. Detached threads self-manage;
61  
    thread creation overhead is acceptable. Detached threads self-manage;
62  
    shared_ptr capture keeps impl alive until completion.
62  
    shared_ptr capture keeps impl alive until completion.
63  

63  

64  
    Cancellation
64  
    Cancellation
65  
    ------------
65  
    ------------
66  
    getaddrinfo() cannot be interrupted mid-call. We use an atomic flag to
66  
    getaddrinfo() cannot be interrupted mid-call. We use an atomic flag to
67  
    indicate cancellation was requested. The worker thread checks this flag
67  
    indicate cancellation was requested. The worker thread checks this flag
68  
    after getaddrinfo() returns and reports the appropriate error.
68  
    after getaddrinfo() returns and reports the appropriate error.
69  

69  

70  
    Class Hierarchy
70  
    Class Hierarchy
71  
    ---------------
71  
    ---------------
72  
    - posix_resolver_service (execution_context service, one per context)
72  
    - posix_resolver_service (execution_context service, one per context)
73  
        - Owns all posix_resolver instances via shared_ptr
73  
        - Owns all posix_resolver instances via shared_ptr
74  
        - Stores scheduler* for posting completions
74  
        - Stores scheduler* for posting completions
75  
    - posix_resolver (one per resolver object)
75  
    - posix_resolver (one per resolver object)
76  
        - Contains embedded resolve_op and reverse_resolve_op for reuse
76  
        - Contains embedded resolve_op and reverse_resolve_op for reuse
77  
        - Uses shared_from_this to prevent premature destruction
77  
        - Uses shared_from_this to prevent premature destruction
78  
    - resolve_op (forward resolution state)
78  
    - resolve_op (forward resolution state)
79  
        - Uses getaddrinfo() to resolve host/service to endpoints
79  
        - Uses getaddrinfo() to resolve host/service to endpoints
80  
    - reverse_resolve_op (reverse resolution state)
80  
    - reverse_resolve_op (reverse resolution state)
81  
        - Uses getnameinfo() to resolve endpoint to host/service
81  
        - Uses getnameinfo() to resolve endpoint to host/service
82  

82  

83  
    Worker Thread Lifetime
83  
    Worker Thread Lifetime
84  
    ----------------------
84  
    ----------------------
85  
    Each resolve() spawns a detached thread. The thread captures a shared_ptr
85  
    Each resolve() spawns a detached thread. The thread captures a shared_ptr
86  
    to posix_resolver, ensuring the impl (and its embedded op_) stays
86  
    to posix_resolver, ensuring the impl (and its embedded op_) stays
87  
    alive until the thread completes, even if the resolver is destroyed.
87  
    alive until the thread completes, even if the resolver is destroyed.
88  

88  

89  
    Completion Flow
89  
    Completion Flow
90  
    ---------------
90  
    ---------------
91  
    Forward resolution:
91  
    Forward resolution:
92  
    1. resolve() sets up op_, spawns worker thread
92  
    1. resolve() sets up op_, spawns worker thread
93  
    2. Worker runs getaddrinfo() (blocking)
93  
    2. Worker runs getaddrinfo() (blocking)
94  
    3. Worker stores results in op_.stored_results
94  
    3. Worker stores results in op_.stored_results
95  
    4. Worker calls svc_.post(&op_) to queue completion
95  
    4. Worker calls svc_.post(&op_) to queue completion
96  
    5. Scheduler invokes op_() which resumes the coroutine
96  
    5. Scheduler invokes op_() which resumes the coroutine
97  

97  

98  
    Reverse resolution follows the same pattern using getnameinfo().
98  
    Reverse resolution follows the same pattern using getnameinfo().
99  

99  

100  
    Single-Inflight Constraint
100  
    Single-Inflight Constraint
101  
    --------------------------
101  
    --------------------------
102  
    Each resolver has ONE embedded op_ for forward and ONE reverse_op_ for
102  
    Each resolver has ONE embedded op_ for forward and ONE reverse_op_ for
103  
    reverse resolution. Concurrent operations of the same type on the same
103  
    reverse resolution. Concurrent operations of the same type on the same
104  
    resolver would corrupt state. Users must serialize operations per-resolver.
104  
    resolver would corrupt state. Users must serialize operations per-resolver.
105  

105  

106  
    Shutdown Synchronization
106  
    Shutdown Synchronization
107  
    ------------------------
107  
    ------------------------
108  
    The service tracks active worker threads via thread_started()/thread_finished().
108  
    The service tracks active worker threads via thread_started()/thread_finished().
109  
    During shutdown(), the service sets shutting_down_ flag and waits for all
109  
    During shutdown(), the service sets shutting_down_ flag and waits for all
110  
    threads to complete before destroying resources.
110  
    threads to complete before destroying resources.
111  
*/
111  
*/
112  

112  

113  
namespace boost::corosio::detail {
113  
namespace boost::corosio::detail {
114  

114  

115  
struct scheduler;
115  
struct scheduler;
116  

116  

117  
namespace posix_resolver_detail {
117  
namespace posix_resolver_detail {
118  

118  

119  
// Convert resolve_flags to addrinfo ai_flags
119  
// Convert resolve_flags to addrinfo ai_flags
120  
int flags_to_hints(resolve_flags flags);
120  
int flags_to_hints(resolve_flags flags);
121  

121  

122  
// Convert reverse_flags to getnameinfo NI_* flags
122  
// Convert reverse_flags to getnameinfo NI_* flags
123  
int flags_to_ni_flags(reverse_flags flags);
123  
int flags_to_ni_flags(reverse_flags flags);
124  

124  

125  
// Convert addrinfo results to resolver_results
125  
// Convert addrinfo results to resolver_results
126  
resolver_results convert_results(
126  
resolver_results convert_results(
127  
    struct addrinfo* ai, std::string_view host, std::string_view service);
127  
    struct addrinfo* ai, std::string_view host, std::string_view service);
128  

128  

129  
// Convert getaddrinfo error codes to std::error_code
129  
// Convert getaddrinfo error codes to std::error_code
130  
std::error_code make_gai_error(int gai_err);
130  
std::error_code make_gai_error(int gai_err);
131  

131  

132  
} // namespace posix_resolver_detail
132  
} // namespace posix_resolver_detail
133  

133  

134  
class posix_resolver_service;
134  
class posix_resolver_service;
135  

135  

136  
/** Resolver implementation for POSIX backends.
136  
/** Resolver implementation for POSIX backends.
137  

137  

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

141  

142  
    @par Single-Inflight Contract
142  
    @par Single-Inflight Contract
143  

143  

144  
    Only ONE resolve operation may be in progress at a time per resolver
144  
    Only ONE resolve operation may be in progress at a time per resolver
145  
    instance. Calling resolve() while a previous resolve() is still pending
145  
    instance. Calling resolve() while a previous resolve() is still pending
146  
    results in undefined behavior:
146  
    results in undefined behavior:
147  

147  

148  
    - The new call overwrites op_ fields (host, service, coroutine handle)
148  
    - The new call overwrites op_ fields (host, service, coroutine handle)
149  
    - The worker thread from the first call reads corrupted state
149  
    - The worker thread from the first call reads corrupted state
150  
    - The wrong coroutine may be resumed, or resumed multiple times
150  
    - The wrong coroutine may be resumed, or resumed multiple times
151  
    - Data races occur on non-atomic op_ members
151  
    - Data races occur on non-atomic op_ members
152  

152  

153  
    @par Safe Usage Patterns
153  
    @par Safe Usage Patterns
154  

154  

155  
    @code
155  
    @code
156  
    // CORRECT: Sequential resolves
156  
    // CORRECT: Sequential resolves
157  
    auto [ec1, r1] = co_await resolver.resolve("host1", "80");
157  
    auto [ec1, r1] = co_await resolver.resolve("host1", "80");
158  
    auto [ec2, r2] = co_await resolver.resolve("host2", "80");
158  
    auto [ec2, r2] = co_await resolver.resolve("host2", "80");
159  

159  

160  
    // CORRECT: Parallel resolves with separate resolver instances
160  
    // CORRECT: Parallel resolves with separate resolver instances
161  
    resolver r1(ctx), r2(ctx);
161  
    resolver r1(ctx), r2(ctx);
162  
    auto [ec1, res1] = co_await r1.resolve("host1", "80");  // in one coroutine
162  
    auto [ec1, res1] = co_await r1.resolve("host1", "80");  // in one coroutine
163  
    auto [ec2, res2] = co_await r2.resolve("host2", "80");  // in another
163  
    auto [ec2, res2] = co_await r2.resolve("host2", "80");  // in another
164  

164  

165  
    // WRONG: Concurrent resolves on same resolver
165  
    // WRONG: Concurrent resolves on same resolver
166  
    // These may run concurrently if launched in parallel - UNDEFINED BEHAVIOR
166  
    // These may run concurrently if launched in parallel - UNDEFINED BEHAVIOR
167  
    auto f1 = resolver.resolve("host1", "80");
167  
    auto f1 = resolver.resolve("host1", "80");
168  
    auto f2 = resolver.resolve("host2", "80");  // BAD: overlaps with f1
168  
    auto f2 = resolver.resolve("host2", "80");  // BAD: overlaps with f1
169  
    @endcode
169  
    @endcode
170  

170  

171  
    @par Thread Safety
171  
    @par Thread Safety
172  
    Distinct objects: Safe.
172  
    Distinct objects: Safe.
173  
    Shared objects: Unsafe. See single-inflight contract above.
173  
    Shared objects: Unsafe. See single-inflight contract above.
174  
*/
174  
*/
175  
class posix_resolver final
175  
class posix_resolver final
176  
    : public resolver::implementation
176  
    : public resolver::implementation
177  
    , public std::enable_shared_from_this<posix_resolver>
177  
    , public std::enable_shared_from_this<posix_resolver>
178  
    , public intrusive_list<posix_resolver>::node
178  
    , public intrusive_list<posix_resolver>::node
179  
{
179  
{
180  
    friend class posix_resolver_service;
180  
    friend class posix_resolver_service;
181  

181  

182  
public:
182  
public:
183  
    // resolve_op - operation state for a single DNS resolution
183  
    // resolve_op - operation state for a single DNS resolution
184  

184  

185  
    struct resolve_op : scheduler_op
185  
    struct resolve_op : scheduler_op
186  
    {
186  
    {
187  
        struct canceller
187  
        struct canceller
188  
        {
188  
        {
189  
            resolve_op* op;
189  
            resolve_op* op;
190  
            void operator()() const noexcept
190  
            void operator()() const noexcept
191  
            {
191  
            {
192  
                op->request_cancel();
192  
                op->request_cancel();
193  
            }
193  
            }
194  
        };
194  
        };
195  

195  

196  
        // Coroutine state
196  
        // Coroutine state
197  
        std::coroutine_handle<> h;
197  
        std::coroutine_handle<> h;
198  
        capy::executor_ref ex;
198  
        capy::executor_ref ex;
199  
        posix_resolver* impl = nullptr;
199  
        posix_resolver* impl = nullptr;
200  

200  

201  
        // Output parameters
201  
        // Output parameters
202  
        std::error_code* ec_out = nullptr;
202  
        std::error_code* ec_out = nullptr;
203  
        resolver_results* out   = nullptr;
203  
        resolver_results* out   = nullptr;
204  

204  

205  
        // Input parameters (owned copies for thread safety)
205  
        // Input parameters (owned copies for thread safety)
206  
        std::string host;
206  
        std::string host;
207  
        std::string service;
207  
        std::string service;
208  
        resolve_flags flags = resolve_flags::none;
208  
        resolve_flags flags = resolve_flags::none;
209  

209  

210  
        // Result storage (populated by worker thread)
210  
        // Result storage (populated by worker thread)
211  
        resolver_results stored_results;
211  
        resolver_results stored_results;
212  
        int gai_error = 0;
212  
        int gai_error = 0;
213  

213  

214  
        // Thread coordination
214  
        // Thread coordination
215  
        std::atomic<bool> cancelled{false};
215  
        std::atomic<bool> cancelled{false};
216  
        std::optional<std::stop_callback<canceller>> stop_cb;
216  
        std::optional<std::stop_callback<canceller>> stop_cb;
217  

217  

218  
        resolve_op() = default;
218  
        resolve_op() = default;
219  

219  

220  
        void reset() noexcept;
220  
        void reset() noexcept;
221  
        void operator()() override;
221  
        void operator()() override;
222  
        void destroy() override;
222  
        void destroy() override;
223  
        void request_cancel() noexcept;
223  
        void request_cancel() noexcept;
224  
        void start(std::stop_token token);
224  
        void start(std::stop_token token);
225  
    };
225  
    };
226  

226  

227  
    // reverse_resolve_op - operation state for reverse DNS resolution
227  
    // reverse_resolve_op - operation state for reverse DNS resolution
228  

228  

229  
    struct reverse_resolve_op : scheduler_op
229  
    struct reverse_resolve_op : scheduler_op
230  
    {
230  
    {
231  
        struct canceller
231  
        struct canceller
232  
        {
232  
        {
233  
            reverse_resolve_op* op;
233  
            reverse_resolve_op* op;
234  
            void operator()() const noexcept
234  
            void operator()() const noexcept
235  
            {
235  
            {
236  
                op->request_cancel();
236  
                op->request_cancel();
237  
            }
237  
            }
238  
        };
238  
        };
239  

239  

240  
        // Coroutine state
240  
        // Coroutine state
241  
        std::coroutine_handle<> h;
241  
        std::coroutine_handle<> h;
242  
        capy::executor_ref ex;
242  
        capy::executor_ref ex;
243  
        posix_resolver* impl = nullptr;
243  
        posix_resolver* impl = nullptr;
244  

244  

245  
        // Output parameters
245  
        // Output parameters
246  
        std::error_code* ec_out             = nullptr;
246  
        std::error_code* ec_out             = nullptr;
247  
        reverse_resolver_result* result_out = nullptr;
247  
        reverse_resolver_result* result_out = nullptr;
248  

248  

249  
        // Input parameters
249  
        // Input parameters
250  
        endpoint ep;
250  
        endpoint ep;
251  
        reverse_flags flags = reverse_flags::none;
251  
        reverse_flags flags = reverse_flags::none;
252  

252  

253  
        // Result storage (populated by worker thread)
253  
        // Result storage (populated by worker thread)
254  
        std::string stored_host;
254  
        std::string stored_host;
255  
        std::string stored_service;
255  
        std::string stored_service;
256  
        int gai_error = 0;
256  
        int gai_error = 0;
257  

257  

258  
        // Thread coordination
258  
        // Thread coordination
259  
        std::atomic<bool> cancelled{false};
259  
        std::atomic<bool> cancelled{false};
260  
        std::optional<std::stop_callback<canceller>> stop_cb;
260  
        std::optional<std::stop_callback<canceller>> stop_cb;
261  

261  

262  
        reverse_resolve_op() = default;
262  
        reverse_resolve_op() = default;
263  

263  

264  
        void reset() noexcept;
264  
        void reset() noexcept;
265  
        void operator()() override;
265  
        void operator()() override;
266  
        void destroy() override;
266  
        void destroy() override;
267  
        void request_cancel() noexcept;
267  
        void request_cancel() noexcept;
268  
        void start(std::stop_token token);
268  
        void start(std::stop_token token);
269  
    };
269  
    };
270  

270  

271  
    explicit posix_resolver(posix_resolver_service& svc) noexcept;
271  
    explicit posix_resolver(posix_resolver_service& svc) noexcept;
272  

272  

273  
    std::coroutine_handle<> resolve(
273  
    std::coroutine_handle<> resolve(
274  
        std::coroutine_handle<>,
274  
        std::coroutine_handle<>,
275  
        capy::executor_ref,
275  
        capy::executor_ref,
276  
        std::string_view host,
276  
        std::string_view host,
277  
        std::string_view service,
277  
        std::string_view service,
278  
        resolve_flags flags,
278  
        resolve_flags flags,
279  
        std::stop_token,
279  
        std::stop_token,
280  
        std::error_code*,
280  
        std::error_code*,
281  
        resolver_results*) override;
281  
        resolver_results*) override;
282  

282  

283  
    std::coroutine_handle<> reverse_resolve(
283  
    std::coroutine_handle<> reverse_resolve(
284  
        std::coroutine_handle<>,
284  
        std::coroutine_handle<>,
285  
        capy::executor_ref,
285  
        capy::executor_ref,
286  
        endpoint const& ep,
286  
        endpoint const& ep,
287  
        reverse_flags flags,
287  
        reverse_flags flags,
288  
        std::stop_token,
288  
        std::stop_token,
289  
        std::error_code*,
289  
        std::error_code*,
290  
        reverse_resolver_result*) override;
290  
        reverse_resolver_result*) override;
291  

291  

292  
    void cancel() noexcept override;
292  
    void cancel() noexcept override;
293  

293  

294  
    resolve_op op_;
294  
    resolve_op op_;
295  
    reverse_resolve_op reverse_op_;
295  
    reverse_resolve_op reverse_op_;
296  

296  

297  
private:
297  
private:
298  
    posix_resolver_service& svc_;
298  
    posix_resolver_service& svc_;
299  
};
299  
};
300  

300  

301  
} // namespace boost::corosio::detail
301  
} // namespace boost::corosio::detail
302  

302  

303  
#endif // BOOST_COROSIO_POSIX
303  
#endif // BOOST_COROSIO_POSIX
304  

304  

305  
#endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_HPP
305  
#endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_HPP