Skip to content

Commit 6eaf03d

Browse files
committed
Add IPv6 socket support with dual-stack, lazy fd creation, and generic socket options
New public types: - tcp: compiled protocol type (family/type/protocol without platform headers); inline native_tcp variant for zero-overhead - socket_option: type-safe wrappers (no_delay, keep_alive, reuse_address, reuse_port, v6_only, linger, send/receive buffer sizes); inline native_socket_option variant Acceptor API: - Split monolithic listen(endpoint) into open() / bind() / listen() so socket options can be set between open and listen - Add convenience constructor: tcp_acceptor(ctx, ep, backlog) for the common open+reuse_address+bind+listen pattern - Add set_option() / get_option() on tcp_acceptor and its implementation interface (all 4 backends) Socket changes: - tcp_socket::open() takes tcp protocol type (defaults to v4) - connect() on a closed socket auto-opens with the endpoint's address family via open(tcp::v6()) / open(tcp::v4()) - Service layer passes (family, type, protocol) triple through socket_service, acceptor_service, and all 4 backends; backends use the caller-provided triple in ::socket() / WSASocketW() - IOCP backend stores address family in win_socket_internal for the ephemeral bind required by ConnectEx - Delete move assignment on io_read_stream / io_write_stream to prevent double-move of virtual base in diamond hierarchy IPv6 support across all backends: - Sockets: IPV6_V6ONLY=1 (v6-only by default, user can clear) - Acceptors: IPV6_V6ONLY=0 (dual-stack by default) - endpoint_convert: IPv4-mapped IPv6 conversion when an IPv4 endpoint connects through an AF_INET6 socket Tests: - IPv6 connect, read/write, v6_only option, dual-stack connect - Lazy open (connect auto-opens), preserves pre-set options - Acceptor: open/bind/listen lifecycle, set/get_option, convenience constructor, IPv6 accept - All existing tests updated for the new open/bind/listen API
1 parent 46745af commit 6eaf03d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+3272
-1155
lines changed

doc/design/physical-structure.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -206,9 +206,13 @@ public:
206206
virtual native_handle_type native_handle() const noexcept = 0;
207207
virtual void cancel() noexcept = 0;
208208

209-
// Protocol-specific socket options
210-
virtual std::error_code set_no_delay( bool ) noexcept = 0;
211-
// ...
209+
// Generic socket options (level/name passed through from option type)
210+
virtual std::error_code set_option(
211+
int level, int optname,
212+
void const* data, std::size_t size ) noexcept = 0;
213+
virtual std::error_code get_option(
214+
int level, int optname,
215+
void* data, std::size_t* size ) const noexcept = 0;
212216
};
213217

214218
private:

include/boost/corosio/detail/acceptor_service.hpp

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,41 @@ class BOOST_COROSIO_DECL acceptor_service
3434
/// Identifies this service for `execution_context` lookup.
3535
using key_type = acceptor_service;
3636

37-
/** Open an acceptor.
37+
/** Create the acceptor socket without binding or listening.
3838
39-
Creates an IPv4 TCP socket, binds it to the specified endpoint,
40-
and begins listening for incoming connections.
39+
Creates a socket with dual-stack enabled for IPv6 but does
40+
not bind or listen. Does not set SO_REUSEADDR.
4141
4242
@param impl The acceptor implementation to open.
43+
@param family Address family (e.g. `AF_INET`, `AF_INET6`).
44+
@param type Socket type (e.g. `SOCK_STREAM`).
45+
@param protocol Protocol number (e.g. `IPPROTO_TCP`).
46+
@return Error code on failure, empty on success.
47+
*/
48+
virtual std::error_code open_acceptor_socket(
49+
tcp_acceptor::implementation& impl,
50+
int family, int type, int protocol) = 0;
51+
52+
/** Bind an open acceptor to a local endpoint.
53+
54+
@param impl The acceptor implementation to bind.
4355
@param ep The local endpoint to bind to.
44-
@param backlog The maximum length of the queue of pending connections.
4556
@return Error code on failure, empty on success.
4657
*/
47-
virtual std::error_code open_acceptor(
48-
tcp_acceptor::implementation& impl, endpoint ep, int backlog) = 0;
58+
virtual std::error_code bind_acceptor(
59+
tcp_acceptor::implementation& impl, endpoint ep) = 0;
60+
61+
/** Start listening for incoming connections.
62+
63+
Registers the acceptor with the platform reactor after
64+
calling `::listen()`.
65+
66+
@param impl The acceptor implementation to listen on.
67+
@param backlog The maximum length of the pending connection queue.
68+
@return Error code on failure, empty on success.
69+
*/
70+
virtual std::error_code listen_acceptor(
71+
tcp_acceptor::implementation& impl, int backlog) = 0;
4972

5073
protected:
5174
/// Construct the acceptor service.

include/boost/corosio/detail/endpoint_convert.hpp

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,145 @@ from_sockaddr_in6(sockaddr_in6 const& sa) noexcept
9090
return endpoint(ipv6_address(bytes), ntohs(sa.sin6_port));
9191
}
9292

93+
/** Convert an IPv4 endpoint to an IPv4-mapped IPv6 sockaddr_in6.
94+
95+
Produces a `sockaddr_in6` with the `::ffff:` prefix, suitable
96+
for passing an IPv4 destination to a dual-stack IPv6 socket.
97+
98+
@param ep The endpoint to convert. Must be IPv4 (is_v4() == true).
99+
@return A sockaddr_in6 with the IPv4-mapped address.
100+
*/
101+
inline sockaddr_in6
102+
to_v4_mapped_sockaddr_in6(endpoint const& ep) noexcept
103+
{
104+
sockaddr_in6 sa{};
105+
sa.sin6_family = AF_INET6;
106+
sa.sin6_port = htons(ep.port());
107+
// ::ffff:0:0/96 prefix
108+
sa.sin6_addr.s6_addr[10] = 0xff;
109+
sa.sin6_addr.s6_addr[11] = 0xff;
110+
auto bytes = ep.v4_address().to_bytes();
111+
std::memcpy(&sa.sin6_addr.s6_addr[12], bytes.data(), 4);
112+
return sa;
113+
}
114+
115+
/** Convert endpoint to sockaddr_storage.
116+
117+
Dispatches to @ref to_sockaddr_in or @ref to_sockaddr_in6
118+
based on the endpoint's address family.
119+
120+
@param ep The endpoint to convert.
121+
@param storage Output parameter filled with the sockaddr.
122+
@return The length of the filled sockaddr structure.
123+
*/
124+
inline socklen_t
125+
to_sockaddr( endpoint const& ep, sockaddr_storage& storage ) noexcept
126+
{
127+
std::memset( &storage, 0, sizeof( storage ) );
128+
if( ep.is_v4() )
129+
{
130+
auto sa = to_sockaddr_in( ep );
131+
std::memcpy( &storage, &sa, sizeof( sa ) );
132+
return sizeof( sa );
133+
}
134+
auto sa6 = to_sockaddr_in6( ep );
135+
std::memcpy( &storage, &sa6, sizeof( sa6 ) );
136+
return sizeof( sa6 );
137+
}
138+
139+
/** Convert endpoint to sockaddr_storage for a specific socket family.
140+
141+
When the socket is AF_INET6 and the endpoint is IPv4, the address
142+
is converted to an IPv4-mapped IPv6 address (`::ffff:x.x.x.x`) so
143+
dual-stack sockets can connect to IPv4 destinations.
144+
145+
@param ep The endpoint to convert.
146+
@param socket_family The address family of the socket (AF_INET or
147+
AF_INET6).
148+
@param storage Output parameter filled with the sockaddr.
149+
@return The length of the filled sockaddr structure.
150+
*/
151+
inline socklen_t
152+
to_sockaddr(
153+
endpoint const& ep,
154+
int socket_family,
155+
sockaddr_storage& storage) noexcept
156+
{
157+
// IPv4 endpoint on IPv6 socket: use IPv4-mapped address
158+
if (ep.is_v4() && socket_family == AF_INET6)
159+
{
160+
std::memset(&storage, 0, sizeof(storage));
161+
auto sa6 = to_v4_mapped_sockaddr_in6(ep);
162+
std::memcpy(&storage, &sa6, sizeof(sa6));
163+
return sizeof(sa6);
164+
}
165+
return to_sockaddr(ep, storage);
166+
}
167+
168+
/** Create endpoint from sockaddr_storage.
169+
170+
Dispatches on `ss_family` to reconstruct the appropriate
171+
IPv4 or IPv6 endpoint.
172+
173+
@param storage The sockaddr_storage with fields in network byte order.
174+
@return An endpoint with address and port extracted from storage.
175+
*/
176+
inline endpoint
177+
from_sockaddr( sockaddr_storage const& storage ) noexcept
178+
{
179+
if( storage.ss_family == AF_INET )
180+
{
181+
sockaddr_in sa;
182+
std::memcpy( &sa, &storage, sizeof( sa ) );
183+
return from_sockaddr_in( sa );
184+
}
185+
if( storage.ss_family == AF_INET6 )
186+
{
187+
sockaddr_in6 sa6;
188+
std::memcpy( &sa6, &storage, sizeof( sa6 ) );
189+
return from_sockaddr_in6( sa6 );
190+
}
191+
return endpoint{};
192+
}
193+
194+
/** Return the native address family for an endpoint.
195+
196+
@param ep The endpoint to query.
197+
@return `AF_INET` for IPv4, `AF_INET6` for IPv6.
198+
*/
199+
inline int
200+
endpoint_family( endpoint const& ep ) noexcept
201+
{
202+
return ep.is_v6() ? AF_INET6 : AF_INET;
203+
}
204+
205+
/** Return the address family of a socket descriptor.
206+
207+
@param fd The socket file descriptor.
208+
@return AF_INET, AF_INET6, or AF_UNSPEC on failure.
209+
*/
210+
inline int
211+
socket_family(
212+
#if BOOST_COROSIO_POSIX
213+
int fd
214+
#else
215+
std::uintptr_t fd
216+
#endif
217+
) noexcept
218+
{
219+
sockaddr_storage storage{};
220+
socklen_t len = sizeof(storage);
221+
if (getsockname(
222+
#if BOOST_COROSIO_POSIX
223+
fd,
224+
#else
225+
static_cast<SOCKET>(fd),
226+
#endif
227+
reinterpret_cast<sockaddr*>(&storage), &len) != 0)
228+
return AF_UNSPEC;
229+
return storage.ss_family;
230+
}
231+
93232
} // namespace boost::corosio::detail
94233

95234
#endif

include/boost/corosio/detail/socket_service.hpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,17 @@ class BOOST_COROSIO_DECL socket_service
3535

3636
/** Open a socket.
3737
38-
Creates an IPv4 TCP socket and associates it with the platform reactor.
38+
Creates a socket and associates it with the platform reactor.
3939
4040
@param impl The socket implementation to open.
41+
@param family Address family (e.g. `AF_INET`, `AF_INET6`).
42+
@param type Socket type (e.g. `SOCK_STREAM`).
43+
@param protocol Protocol number (e.g. `IPPROTO_TCP`).
4144
@return Error code on failure, empty on success.
4245
*/
43-
virtual std::error_code open_socket(tcp_socket::implementation& impl) = 0;
46+
virtual std::error_code
47+
open_socket( tcp_socket::implementation& impl,
48+
int family, int type, int protocol ) = 0;
4449

4550
protected:
4651
/// Construct the socket service.

include/boost/corosio/io/io_read_stream.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ class BOOST_COROSIO_DECL io_read_stream : virtual public io_object
105105
/// Construct from a handle.
106106
explicit io_read_stream(handle h) noexcept : io_object(std::move(h)) {}
107107

108+
io_read_stream(io_read_stream&&) noexcept = default;
109+
io_read_stream& operator=(io_read_stream&&) noexcept = delete;
110+
io_read_stream(io_read_stream const&) = delete;
111+
io_read_stream& operator=(io_read_stream const&) = delete;
112+
108113
public:
109114
/** Asynchronously read data from the stream.
110115

include/boost/corosio/io/io_write_stream.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ class BOOST_COROSIO_DECL io_write_stream : virtual public io_object
105105
/// Construct from a handle.
106106
explicit io_write_stream(handle h) noexcept : io_object(std::move(h)) {}
107107

108+
io_write_stream(io_write_stream&&) noexcept = default;
109+
io_write_stream& operator=(io_write_stream&&) noexcept = delete;
110+
io_write_stream(io_write_stream const&) = delete;
111+
io_write_stream& operator=(io_write_stream const&) = delete;
112+
108113
public:
109114
/** Asynchronously write data to the stream.
110115

include/boost/corosio/ipv6_address.hpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,18 @@ class BOOST_COROSIO_DECL ipv6_address
261261
return a1.addr_ != a2.addr_;
262262
}
263263

264+
/** Return an address object that represents the unspecified address.
265+
266+
The address 0:0:0:0:0:0:0:0 (::) may be used to bind a socket
267+
to all available interfaces.
268+
269+
@return The unspecified address (::).
270+
*/
271+
static ipv6_address any() noexcept
272+
{
273+
return ipv6_address();
274+
}
275+
264276
/** Return an address object that represents the loopback address.
265277
266278
The unicast address 0:0:0:0:0:0:0:1 is called

include/boost/corosio/native/detail/epoll/epoll_acceptor.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,13 @@ class epoll_acceptor final
5757
return fd_ >= 0;
5858
}
5959
void cancel() noexcept override;
60+
61+
std::error_code set_option(
62+
int level, int optname,
63+
void const* data, std::size_t size) noexcept override;
64+
std::error_code get_option(
65+
int level, int optname,
66+
void* data, std::size_t* size) const noexcept override;
6067
void cancel_single_op(epoll_op& op) noexcept;
6168
void close_socket() noexcept;
6269
void set_local_endpoint(endpoint ep) noexcept

0 commit comments

Comments
 (0)