Skip to content

Commit cfab825

Browse files
committed
Send multicast packets to each interface
1 parent a90e967 commit cfab825

File tree

5 files changed

+95
-25
lines changed

5 files changed

+95
-25
lines changed

src/api_config.cpp

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "api_config.h"
22
#include "common.h"
3+
#include "util/cast.hpp"
34
#include "util/inireader.hpp"
45
#include <algorithm>
56
#include <cstdlib>
@@ -184,6 +185,37 @@ void api_config::load_from_file(const std::string &filename) {
184185
if (ttl_override >= 0) multicast_ttl_ = ttl_override;
185186
if (!address_override.empty()) multicast_addresses_ = address_override;
186187

188+
// The network stack requires the source interfaces for multicast packets to be
189+
// specified as IPv4 address or an IPv6 interface index
190+
// Try getting the interfaces from the configuration files
191+
using namespace lslboost::asio::ip;
192+
std::vector<std::string> netifs = parse_set(pt.get("multicast.Interfaces", "{}"));
193+
for (const auto &netifstr : netifs) {
194+
netif if_;
195+
if_.name = std::string("Configured in lslapi.cfg");
196+
if_.addr = make_address(netifstr);
197+
if (if_.addr.is_v6()) if_.ifindex = if_.addr.to_v6().scope_id();
198+
multicast_interfaces.push_back(if_);
199+
}
200+
// Try getting the interfaces from the OS
201+
if (multicast_interfaces.empty()) multicast_interfaces = get_local_interfaces();
202+
203+
// Otherwise, let the OS select an appropriate network interface
204+
if (multicast_interfaces.empty()) {
205+
LOG_F(ERROR,
206+
"No local network interface addresses found, resolving streams will likely "
207+
"only work for devices connected to the main network adapter\n");
208+
// Add dummy interface with default settings
209+
netif dummy;
210+
dummy.name = "Dummy interface";
211+
dummy.addr = address_v4::any();
212+
multicast_interfaces.push_back(dummy);
213+
dummy.name = "IPv6 dummy interface";
214+
dummy.addr = address_v6::any();
215+
multicast_interfaces.push_back(dummy);
216+
}
217+
218+
187219
// read the [lab] settings
188220
known_peers_ = parse_set(pt.get("lab.KnownPeers", "{}"));
189221
session_id_ = pt.get("lab.SessionID", "default");
@@ -213,7 +245,7 @@ void api_config::load_from_file(const std::string &filename) {
213245
force_default_timestamps_ = pt.get("tuning.ForceDefaultTimestamps", false);
214246

215247
// read the [log] settings
216-
int log_level = pt.get("log.level", (int) loguru::Verbosity_INFO);
248+
int log_level = pt.get("log.level", (int)loguru::Verbosity_INFO);
217249
if (log_level < -3 || log_level > 9)
218250
throw std::runtime_error("Invalid log.level (valid range: -3 to 9");
219251

src/api_config.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
#ifndef API_CONFIG_H
22
#define API_CONFIG_H
33

4+
#include "netinterfaces.h"
45
#include <cstdint>
6+
#include <loguru.hpp>
57
#include <string>
68
#include <vector>
79

@@ -106,8 +108,16 @@ class api_config {
106108
const std::string &listen_address() const { return listen_address_; }
107109

108110
/**
109-
* @brief The TTL setting (time-to-live) for the multicast packets.
111+
* A list of local interface addresses the multicast packets should be
112+
* sent from.
110113
*
114+
* The ini file may contain IPv4 addresses and/or IPv6 addresses with the
115+
* interface index as scope id, e.g. `1234:5678::2%3`
116+
**/
117+
std::vector<lsl::netif> multicast_interfaces;
118+
119+
/**
120+
* The TTL setting (time-to-live) for the multicast packets.
111121
* This is determined according to the ResolveScope setting if not overridden by the TTLOverride
112122
* setting. The higher this number (0-255), the broader their distribution. Routers (if
113123
* correctly configured) employ various thresholds below which packets are not further

src/resolve_attempt_udp.cpp

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "resolve_attempt_udp.h"
22
#include "api_config.h"
3+
#include "netinterfaces.h"
34
#include "resolver_impl.h"
45
#include "socket_utils.h"
56
#include <boost/asio/io_context.hpp>
@@ -10,13 +11,18 @@
1011
#include <sstream>
1112

1213
using namespace lsl;
14+
namespace asio = lslboost::asio;
15+
using err_t = const lslboost::system::error_code &;
16+
using asio::ip::multicast::outbound_interface;
1317

1418
resolve_attempt_udp::resolve_attempt_udp(asio::io_context &io, const udp &protocol,
1519
const std::vector<udp::endpoint> &targets, const std::string &query, result_container &results,
1620
std::mutex &results_mut, double cancel_after, cancellable_registry *registry)
1721
: io_(io), results_(results), results_mut_(results_mut), cancel_after_(cancel_after),
1822
cancelled_(false), targets_(targets), query_(query), unicast_socket_(io),
19-
broadcast_socket_(io), multicast_socket_(io), recv_socket_(io), cancel_timer_(io) {
23+
broadcast_socket_(io), multicast_socket_(io),
24+
multicast_interfaces(api_config::get_instance()->multicast_interfaces), recv_socket_(io),
25+
cancel_timer_(io) {
2026
// open the sockets that we might need
2127
recv_socket_.open(protocol);
2228
try {
@@ -70,7 +76,7 @@ void resolve_attempt_udp::begin() {
7076
// initiate the result gathering chain
7177
receive_next_result();
7278
// initiate the send chain
73-
send_next_query(targets_.begin());
79+
send_next_query(targets_.begin(), multicast_interfaces.begin());
7480

7581
// also initiate the cancel event, if desired
7682
if (cancel_after_ != FOREVER) {
@@ -144,27 +150,41 @@ void resolve_attempt_udp::handle_receive_outcome(err_t err, std::size_t len) {
144150

145151
// === send loop ===
146152

147-
void resolve_attempt_udp::send_next_query(endpoint_list::const_iterator next) {
148-
if (next == targets_.end() || cancelled_) return;
149-
150-
udp::endpoint ep(*next++);
151-
// endpoint matches our active protocol?
152-
if (ep.protocol() == recv_socket_.local_endpoint().protocol()) {
153-
// select socket to use
154-
udp::socket &sock =
155-
(ep.address() == asio::ip::address_v4::broadcast())
156-
? broadcast_socket_
157-
: (ep.address().is_multicast() ? multicast_socket_ : unicast_socket_);
158-
// and send the query over it
159-
sock.async_send_to(asio::buffer(query_msg_), ep,
160-
[shared_this = shared_from_this(), next](err_t err, size_t /*unused*/) {
161-
if (!shared_this->cancelled_ && err != asio::error::operation_aborted &&
162-
err != asio::error::not_connected && err != asio::error::not_socket)
163-
shared_this->send_next_query(next);
164-
});
153+
void resolve_attempt_udp::send_next_query(
154+
endpoint_list::const_iterator next, mcast_interface_list::const_iterator mcit) {
155+
if (cancelled_ || mcit == multicast_interfaces.end()) return;
156+
auto proto = recv_socket_.local_endpoint().protocol();
157+
if (next == targets_.begin()) {
158+
// Mismatching protocols? Skip this round
159+
if (mcit->addr.is_v4() != (proto == asio::ip::udp::v4()))
160+
next = targets_.end();
161+
else
162+
multicast_socket_.set_option(mcit->addr.is_v4() ? outbound_interface(mcit->addr.to_v4())
163+
: outbound_interface(mcit->ifindex));
164+
}
165+
if (next != targets_.end()) {
166+
udp::endpoint ep(*next++);
167+
// endpoint matches our active protocol?
168+
if (ep.protocol() == recv_socket_.local_endpoint().protocol()) {
169+
// select socket to use
170+
udp::socket &sock =
171+
(ep.address() == asio::ip::address_v4::broadcast())
172+
? broadcast_socket_
173+
: (ep.address().is_multicast() ? multicast_socket_ : unicast_socket_);
174+
// and send the query over it
175+
auto keepalive(shared_from_this());
176+
sock.async_send_to(lslboost::asio::buffer(query_msg_), ep,
177+
[shared_this = shared_from_this(), next, mcit](err_t err, size_t /*unused*/) {
178+
if (!shared_this->cancelled_ && err != asio::error::operation_aborted &&
179+
err != asio::error::not_connected && err != asio::error::not_socket)
180+
shared_this->send_next_query(next, mcit);
181+
});
182+
} else
183+
// otherwise just go directly to the next query
184+
send_next_query(next, mcit);
165185
} else
166-
// otherwise just go directly to the next query
167-
send_next_query(next);
186+
// Restart from the next interface
187+
send_next_query(targets_.begin(), ++mcit);
168188
}
169189

170190
void resolve_attempt_udp::do_cancel() {

src/resolve_attempt_udp.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33

44
#include "cancellation.h"
55
#include "forward.h"
6+
#include "netinterfaces.h"
67
#include "stream_info_impl.h"
8+
#include <boost/asio/ip/multicast.hpp>
79
#include <boost/asio/ip/udp.hpp>
810
#include <boost/asio/steady_timer.hpp>
911
#include <map>
@@ -18,6 +20,8 @@ namespace lsl {
1820

1921
/// A container for resolve results (map from stream instance UID onto (stream_info,receive-time)).
2022
typedef std::map<std::string, std::pair<stream_info_impl, double>> result_container;
23+
/// A container for outgoing multicast interfaces
24+
typedef std::vector<class netif> mcast_interface_list;
2125

2226
/**
2327
* An asynchronous resolve attempt for a single query targeted at a set of endpoints, via UDP.
@@ -76,7 +80,8 @@ class resolve_attempt_udp : public cancellable_obj,
7680
void receive_next_result();
7781

7882
/// Thos function starts an async send operation for the given current endpoint.
79-
void send_next_query(endpoint_list::const_iterator next);
83+
void send_next_query(
84+
endpoint_list::const_iterator next, mcast_interface_list::const_iterator mcit);
8085

8186
/// Handler that gets called when a receive has completed.
8287
void handle_receive_outcome(err_t err, std::size_t len);
@@ -122,6 +127,8 @@ class resolve_attempt_udp : public cancellable_obj,
122127
udp::socket broadcast_socket_;
123128
/// socket to send data over (for multicasts)
124129
udp::socket multicast_socket_;
130+
/// Interface addresses to send multicast packets from
131+
const mcast_interface_list &multicast_interfaces;
125132
/// socket to receive replies (always unicast)
126133
udp::socket recv_socket_;
127134
/// timer to schedule the cancel action

src/util/cast.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,6 @@ template signed char from_string(const std::string &);
3636
template char from_string(const std::string &);
3737
template int16_t from_string(const std::string &);
3838
template int32_t from_string(const std::string &);
39+
template uint32_t from_string(const std::string &);
3940
template int64_t from_string(const std::string &);
4041
} // namespace lsl

0 commit comments

Comments
 (0)