Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions include/boost/corosio/detail/tcp_service.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ class BOOST_COROSIO_DECL tcp_service
int type,
int protocol) = 0;

/** Bind a stream socket to a local endpoint.

@param impl The socket implementation to bind.
@param ep The local endpoint to bind to.
@return Error code on failure, empty on success.
*/
virtual std::error_code
bind_socket(tcp_socket::implementation& impl, endpoint ep) = 0;

protected:
/// Construct the TCP service.
tcp_service() = default;
Expand Down
10 changes: 10 additions & 0 deletions include/boost/corosio/native/detail/epoll/epoll_tcp_service.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ class BOOST_COROSIO_DECL epoll_tcp_service final
int family,
int type,
int protocol) override;

std::error_code
bind_socket(tcp_socket::implementation& impl, endpoint ep) override;
};

inline void
Expand Down Expand Up @@ -233,6 +236,13 @@ epoll_tcp_service::open_socket(
return {};
}

inline std::error_code
epoll_tcp_service::bind_socket(
tcp_socket::implementation& impl, endpoint ep)
{
return static_cast<epoll_tcp_socket*>(&impl)->do_bind(ep);
}

} // namespace boost::corosio::detail

#endif // BOOST_COROSIO_HAS_EPOLL
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -403,33 +403,38 @@ win_tcp_socket_internal::connect(

svc_.work_started();

// Ephemeral bind — must match the socket's family, not the endpoint's
sockaddr_storage bind_storage{};
socklen_t bind_len;
if (family_ == AF_INET6)
// ConnectEx requires the socket to be bound. Skip if already bound
// (e.g. the caller used tcp_socket::bind() before connect).
if (local_endpoint_ == endpoint{})
{
sockaddr_in6 sa6{};
sa6.sin6_family = AF_INET6;
sa6.sin6_port = 0;
sa6.sin6_addr = in6addr_any;
std::memcpy(&bind_storage, &sa6, sizeof(sa6));
bind_len = sizeof(sa6);
}
else
{
sockaddr_in sa4{};
sa4.sin_family = AF_INET;
sa4.sin_addr.s_addr = INADDR_ANY;
sa4.sin_port = 0;
std::memcpy(&bind_storage, &sa4, sizeof(sa4));
bind_len = sizeof(sa4);
}
sockaddr_storage bind_storage{};
socklen_t bind_len;
if (family_ == AF_INET6)
{
sockaddr_in6 sa6{};
sa6.sin6_family = AF_INET6;
sa6.sin6_port = 0;
sa6.sin6_addr = in6addr_any;
std::memcpy(&bind_storage, &sa6, sizeof(sa6));
bind_len = sizeof(sa6);
}
else
{
sockaddr_in sa4{};
sa4.sin_family = AF_INET;
sa4.sin_addr.s_addr = INADDR_ANY;
sa4.sin_port = 0;
std::memcpy(&bind_storage, &sa4, sizeof(sa4));
bind_len = sizeof(sa4);
}

if (::bind(socket_, reinterpret_cast<sockaddr*>(&bind_storage), bind_len) ==
SOCKET_ERROR)
{
svc_.on_completion(&op, ::WSAGetLastError(), 0);
return std::noop_coroutine();
if (::bind(
socket_, reinterpret_cast<sockaddr*>(&bind_storage),
bind_len) == SOCKET_ERROR)
{
svc_.on_completion(&op, ::WSAGetLastError(), 0);
return std::noop_coroutine();
}
}

auto connect_ex = svc_.connect_ex();
Expand Down Expand Up @@ -884,6 +889,28 @@ win_tcp_service::open_socket(
return {};
}

inline std::error_code
win_tcp_service::bind_socket(win_tcp_socket_internal& impl, endpoint ep)
{
SOCKET sock = impl.socket_;

sockaddr_storage storage{};
socklen_t addrlen = detail::to_sockaddr(ep, storage);
if (::bind(
sock, reinterpret_cast<sockaddr*>(&storage),
static_cast<int>(addrlen)) == SOCKET_ERROR)
return make_err(::WSAGetLastError());

// Cache local endpoint (resolves ephemeral port)
sockaddr_storage local_storage{};
int local_len = sizeof(local_storage);
if (::getsockname(
sock, reinterpret_cast<sockaddr*>(&local_storage), &local_len) == 0)
impl.local_endpoint_ = detail::from_sockaddr(local_storage);

return {};
}

inline void*
win_tcp_service::native_handle() const noexcept
{
Expand Down
9 changes: 9 additions & 0 deletions include/boost/corosio/native/detail/iocp/win_tcp_service.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,15 @@ class BOOST_COROSIO_DECL win_tcp_service final
std::error_code
open_socket(win_tcp_socket_internal& impl, int family, int type, int protocol);

/** Bind a stream socket to a local endpoint.

@param impl The socket implementation internal to bind.
@param ep The local endpoint to bind to.
@return Error code, or success.
*/
std::error_code
bind_socket(win_tcp_socket_internal& impl, endpoint ep);

/** Destroy an acceptor implementation wrapper.
Removes from tracking list and deletes.
*/
Expand Down
10 changes: 10 additions & 0 deletions include/boost/corosio/native/detail/kqueue/kqueue_tcp_service.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ class BOOST_COROSIO_DECL kqueue_tcp_service final
int family,
int type,
int protocol) override;

std::error_code
bind_socket(tcp_socket::implementation& impl, endpoint ep) override;
};

inline void
Expand Down Expand Up @@ -340,6 +343,13 @@ kqueue_tcp_service::open_socket(
return {};
}

inline std::error_code
kqueue_tcp_service::bind_socket(
tcp_socket::implementation& impl, endpoint ep)
{
return static_cast<kqueue_tcp_socket*>(&impl)->do_bind(ep);
}

} // namespace boost::corosio::detail

#endif // BOOST_COROSIO_HAS_KQUEUE
Expand Down
10 changes: 10 additions & 0 deletions include/boost/corosio/native/detail/select/select_tcp_service.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ class BOOST_COROSIO_DECL select_tcp_service final
int family,
int type,
int protocol) override;

std::error_code
bind_socket(tcp_socket::implementation& impl, endpoint ep) override;
};

inline void
Expand Down Expand Up @@ -234,6 +237,13 @@ select_tcp_service::open_socket(
return {};
}

inline std::error_code
select_tcp_service::bind_socket(
tcp_socket::implementation& impl, endpoint ep)
{
return static_cast<select_tcp_socket*>(&impl)->do_bind(ep);
}

} // namespace boost::corosio::detail

#endif // BOOST_COROSIO_HAS_SELECT
Expand Down
22 changes: 22 additions & 0 deletions include/boost/corosio/tcp_socket.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,28 @@ class BOOST_COROSIO_DECL tcp_socket : public io_stream
*/
void open(tcp proto = tcp::v4());

/** Bind the socket to a local endpoint.

Associates the socket with a local address and port before
connecting. Useful for multi-homed hosts or source-port
pinning.

@param ep The local endpoint to bind to.

@return An error code indicating success or the reason for
failure.

@par Error Conditions
@li `errc::address_in_use`: The endpoint is already in use.
@li `errc::address_not_available`: The address is not
available on any local interface.
@li `errc::permission_denied`: Insufficient privileges to
bind to the endpoint (e.g., privileged port).

@throws std::logic_error if the socket is not open.
*/
[[nodiscard]] std::error_code bind(endpoint ep);

/** Close the socket.

Releases socket resources. Any pending operations complete
Expand Down
2 changes: 1 addition & 1 deletion include/boost/corosio/udp_socket.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ class BOOST_COROSIO_DECL udp_socket : public io_object

@throws std::logic_error if the socket is not open.
*/
std::error_code bind(endpoint ep);
[[nodiscard]] std::error_code bind(endpoint ep);

/** Cancel any pending asynchronous operations.

Expand Down
17 changes: 17 additions & 0 deletions src/corosio/src/tcp_socket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,23 @@ tcp_socket::open_for_family(int family, int type, int protocol)
detail::throw_system_error(ec, "tcp_socket::open");
}

std::error_code
tcp_socket::bind(endpoint ep)
{
if (!is_open())
detail::throw_logic_error("bind: socket not open");
#if BOOST_COROSIO_HAS_IOCP
auto& svc = static_cast<detail::win_tcp_service&>(h_.service());
auto& wrapper = static_cast<tcp_socket::implementation&>(*h_.get());
return svc.bind_socket(
*static_cast<detail::win_tcp_socket&>(wrapper).get_internal(), ep);
#else
auto& svc = static_cast<detail::tcp_service&>(h_.service());
return svc.bind_socket(
static_cast<tcp_socket::implementation&>(*h_.get()), ep);
#endif
}

void
tcp_socket::close()
{
Expand Down
40 changes: 40 additions & 0 deletions test/unit/tcp_acceptor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,44 @@ struct tcp_acceptor_test
acc.close();
}

void testBindClosedAcceptorThrows()
{
io_context ioc(Backend);
tcp_acceptor acc(ioc);

bool caught = false;
try
{
auto ec = acc.bind(endpoint(ipv4_address::loopback(), 0));
(void)ec;
}
catch (std::logic_error const&)
{
caught = true;
}
BOOST_TEST(caught);
}

void testBindAddressInUse()
{
io_context ioc(Backend);

tcp_acceptor acc1(ioc);
acc1.open();
acc1.set_option(socket_option::reuse_address(true));
auto ec = acc1.bind(endpoint(ipv4_address::loopback(), 0));
BOOST_TEST(!ec);
auto port = acc1.local_endpoint().port();

tcp_acceptor acc2(ioc);
acc2.open();
ec = acc2.bind(endpoint(ipv4_address::loopback(), port));
BOOST_TEST(ec);

acc1.close();
acc2.close();
}

void testBindError()
{
io_context ioc(Backend);
Expand Down Expand Up @@ -602,6 +640,8 @@ struct tcp_acceptor_test

// Explicit bind+listen flow
testBindThenListen();
testBindClosedAcceptorThrows();
testBindAddressInUse();
testBindError();
}
};
Expand Down
Loading
Loading