Skip to content
Closed
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
6 changes: 6 additions & 0 deletions include/lsl/outlet.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ extern LIBLSL_C_API lsl_outlet lsl_create_outlet(lsl_streaminfo info, int32_t ch
*/
extern LIBLSL_C_API lsl_outlet lsl_create_outlet_ex(
lsl_streaminfo info, int32_t chunk_size, int32_t max_buffered, lsl_transport_options_t flags);
/** @copydoc lsl_create_outlet()
* @param listen_address The local IP address to bind to (e.g. "192.168.1.5").
* Pass NULL or "" to use the default from the API config (usually all interfaces).
*/
extern LIBLSL_C_API lsl_outlet lsl_create_outlet_bound(
lsl_streaminfo info, int32_t chunk_size, int32_t max_buffered, const char *listen_address);

/**
* Destroy an outlet.
Expand Down
7 changes: 7 additions & 0 deletions src/lsl_outlet_c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ LIBLSL_C_API lsl_outlet lsl_create_outlet(
return lsl_create_outlet_ex(info, chunk_size, max_buffered, transp_default);
}

LIBLSL_C_API lsl_outlet lsl_create_outlet_bound(
lsl_streaminfo info, int32_t chunk_size, int32_t max_buffered, const char *listen_address) {
return create_object_noexcept<stream_outlet_impl>(
*info, chunk_size, max_buffered, transp_default,
listen_address ? std::string(listen_address) : std::string{});
}

LIBLSL_C_API void lsl_destroy_outlet(lsl_outlet out) {
try {
delete out;
Expand Down
26 changes: 26 additions & 0 deletions src/socket_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,29 @@ uint16_t lsl::bind_and_listen_to_port_in_range(
acc.listen(backlog);
return port;
}

uint16_t lsl::bind_and_listen_to_port_in_range(
tcp_acceptor &acc, asio::ip::address addr, int backlog) {
const auto *cfg = lsl::api_config::get_instance();
asio::error_code ec;
for (uint16_t port = cfg->base_port(), e = port + cfg->port_range(); port < e; port++) {
acc.bind(asio::ip::tcp::endpoint(addr, port), ec);
if (ec == asio::error::address_in_use) continue;
if (!ec) {
acc.listen(backlog);
return port;
}
}
if (cfg->allow_random_ports()) {
acc.bind(asio::ip::tcp::endpoint(addr, 0), ec);
if (!ec) {
acc.listen(backlog);
return acc.local_endpoint().port();
}
}
throw std::runtime_error(
"All local ports were found occupied. You may have more open outlets on this machine "
"than your PortRange setting allows (see "
"https://labstreaminglayer.readthedocs.io/info/network-connectivity.html"
") or you have a problem with your network configuration.");
}
5 changes: 5 additions & 0 deletions src/socket_utils.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef SOCKET_UTILS_H
#define SOCKET_UTILS_H

#include <asio/ip/address.hpp>
#include <asio/ip/tcp.hpp>
#include <asio/ip/udp.hpp>

Expand All @@ -19,6 +20,10 @@ uint16_t bind_port_in_range(udp_socket &sock, asio::ip::udp protocol);
/// Bind and listen to an acceptor on a free port in the configured port range or throw an error.
uint16_t bind_and_listen_to_port_in_range(
tcp_acceptor &acc, asio::ip::tcp protocol, int backlog);

/// Bind and listen to an acceptor on a specific local address, free port in the configured range.
uint16_t bind_and_listen_to_port_in_range(
tcp_acceptor &acc, asio::ip::address addr, int backlog);
} // namespace lsl

#endif
19 changes: 11 additions & 8 deletions src/stream_outlet_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@
namespace lsl {

stream_outlet_impl::stream_outlet_impl(const stream_info_impl &info, int32_t chunk_size,
int32_t requested_bufsize, lsl_transport_options_t flags)
: sample_factory_(std::make_shared<factory>(info.channel_format(), info.channel_count(),
int32_t requested_bufsize, lsl_transport_options_t flags, std::string listen_address)
: listen_address_(std::move(listen_address)),
sample_factory_(std::make_shared<factory>(info.channel_format(), info.channel_count(),
static_cast<uint32_t>(
info.nominal_srate()
? info.nominal_srate() * api_config::get_instance()->outlet_buffer_reserve_ms() /
Expand All @@ -41,21 +42,25 @@ stream_outlet_impl::stream_outlet_impl(const stream_info_impl &info, int32_t chu

const api_config *cfg = api_config::get_instance();

// resolve effective listen address: explicit override takes priority over api_config
const std::string &effective_listen =
listen_address_.empty() ? cfg->listen_address() : listen_address_;

// instantiate IPv4 and/or IPv6 stacks (depending on settings)
if (cfg->allow_ipv4()) try {
instantiate_stack(udp::v4());
instantiate_stack(udp::v4(), effective_listen);
} catch (std::exception &e) {
LOG_F(WARNING, "Could not instantiate IPv4 stack: %s", e.what());
}
if (cfg->allow_ipv6()) try {
instantiate_stack(udp::v6());
instantiate_stack(udp::v6(), effective_listen);
} catch (std::exception &e) {
LOG_F(WARNING, "Could not instantiate IPv6 stack: %s", e.what());
}

// create TCP data server
tcp_server_ = std::make_shared<tcp_server>(info_, io_ctx_data_, send_buffer_, sample_factory_,
chunk_size_, cfg->allow_ipv4(), cfg->allow_ipv6(), sync_mode_);
chunk_size_, cfg->allow_ipv4(), cfg->allow_ipv6(), sync_mode_, effective_listen);

// fail if both stacks failed to instantiate
if (udp_servers_.empty())
Expand All @@ -82,10 +87,8 @@ stream_outlet_impl::stream_outlet_impl(const stream_info_impl &info, int32_t chu
}));
}

void stream_outlet_impl::instantiate_stack(udp udp_protocol) {
// get api_config
void stream_outlet_impl::instantiate_stack(udp udp_protocol, const std::string &listen_address) {
const api_config *cfg = api_config::get_instance();
std::string listen_address = cfg->listen_address();
int multicast_ttl = cfg->multicast_ttl();
uint16_t multicast_port = cfg->multicast_port();
LOG_F(2, "%s: Trying to listen at address '%s'", info().name().c_str(), listen_address.c_str());
Expand Down
8 changes: 6 additions & 2 deletions src/stream_outlet_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,11 @@ class stream_outlet_impl {
* to hold a bit more than 15 minutes of data at 512Hz, while consuming not more than ca. 512MB
* of RAM. Depends on `flags` as calculated in `stream_info_impl::calc_transport_buf_samples()`
* @param flags Bitwise-OR'd flags from lsl_transport_options_t
* @param listen_address Local IP address to bind to; empty string means all interfaces.
*/
stream_outlet_impl(const stream_info_impl &info, int32_t chunk_size = 0,
int32_t requested_bufsize = 900, lsl_transport_options_t flags = transp_default);
int32_t requested_bufsize = 900, lsl_transport_options_t flags = transp_default,
std::string listen_address = {});

/**
* Destructor.
Expand Down Expand Up @@ -313,7 +315,7 @@ class stream_outlet_impl {

private:
/// Instantiate a new server stack.
void instantiate_stack(udp udp_protocol);
void instantiate_stack(udp udp_protocol, const std::string &listen_address);

/// Allocate and enqueue a new sample into the send buffer.
template <class T> void enqueue(const T *data, double timestamp, bool pushthrough);
Expand Down Expand Up @@ -346,6 +348,8 @@ class stream_outlet_impl {
"stream's number of channels.");
}

/// local IP address to bind to; empty means all interfaces
std::string listen_address_;
/// a factory for samples of appropriate type
factory_p sample_factory_;
/// the preferred chunk size
Expand Down
22 changes: 18 additions & 4 deletions src/tcp_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -310,8 +310,10 @@ class client_session : public std::enable_shared_from_this<client_session> {
};

tcp_server::tcp_server(stream_info_impl_p info, io_context_p io, send_buffer_p sendbuf,
factory_p factory, int chunk_size, bool allow_v4, bool allow_v6, bool do_sync)
: chunk_size_(chunk_size), info_(std::move(info)), io_(std::move(io)),
factory_p factory, int chunk_size, bool allow_v4, bool allow_v6, bool do_sync,
std::string listen_address)
: listen_address_(std::move(listen_address)), chunk_size_(chunk_size),
info_(std::move(info)), io_(std::move(io)),
factory_(std::move(factory)), send_buffer_(std::move(sendbuf)) {
// Create sync handler if sync mode is requested
if (do_sync) {
Expand All @@ -333,7 +335,13 @@ tcp_server::tcp_server(stream_info_impl_p info, io_context_p io, send_buffer_p s
if (allow_v4) {
try {
acceptor_v4_ = std::make_unique<tcp_acceptor>(*io_, asio::ip::tcp::v4());
auto port = bind_and_listen_to_port_in_range(*acceptor_v4_, asio::ip::tcp::v4(), 10);
uint16_t port;
if (!listen_address_.empty()) {
auto addr = asio::ip::make_address(listen_address_);
port = bind_and_listen_to_port_in_range(*acceptor_v4_, addr, 10);
} else {
port = bind_and_listen_to_port_in_range(*acceptor_v4_, asio::ip::tcp::v4(), 10);
}
info_->v4data_port(port);
LOG_F(1, "Created IPv%d TCP acceptor for %s @ port %d", 4, info_->name().c_str(), port);
} catch (std::exception &e) {
Expand All @@ -344,7 +352,13 @@ tcp_server::tcp_server(stream_info_impl_p info, io_context_p io, send_buffer_p s
if (allow_v6) {
try {
acceptor_v6_ = std::make_unique<tcp_acceptor>(*io_, asio::ip::tcp::v6());
auto port = bind_and_listen_to_port_in_range(*acceptor_v6_, asio::ip::tcp::v6(), 10);
uint16_t port;
if (!listen_address_.empty()) {
auto addr = asio::ip::make_address(listen_address_);
port = bind_and_listen_to_port_in_range(*acceptor_v6_, addr, 10);
} else {
port = bind_and_listen_to_port_in_range(*acceptor_v6_, asio::ip::tcp::v6(), 10);
}
info_->v6data_port(port);
LOG_F(1, "Created IPv%d TCP acceptor for %s @ port %d", 6, info_->name().c_str(), port);
} catch (std::exception &e) {
Expand Down
6 changes: 5 additions & 1 deletion src/tcp_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ class tcp_server : public std::enable_shared_from_this<tcp_server> {
* @param do_sync If true, use synchronous (blocking) socket writes for zero-copy transfer.
*/
tcp_server(stream_info_impl_p info, io_context_p io, send_buffer_p sendbuf, factory_p factory,
int chunk_size, bool allow_v4, bool allow_v6, bool do_sync = false);
int chunk_size, bool allow_v4, bool allow_v6, bool do_sync = false,
std::string listen_address = {});

/// Destructor (must be defined in .cpp due to unique_ptr to incomplete type)
~tcp_server();
Expand Down Expand Up @@ -106,6 +107,9 @@ class tcp_server : public std::enable_shared_from_this<tcp_server> {
/// Post a close of all in-flight sockets.
void close_inflight_sessions();

// local IP address to bind to; empty means all interfaces
std::string listen_address_;

// data used by the transfer threads
int chunk_size_; // the chunk size to use (or 0)

Expand Down