22 #include <netinet/in.h>
25 #include <arpa/inet.h>
26 #include "spdlog/spdlog.h"
37 unsigned int resolve_iface_index(
const std::string& iface_address) {
38 if (iface_address.empty() || iface_address ==
"0.0.0.0" || iface_address ==
"::") {
41 struct ifaddrs* ifaddr =
nullptr;
42 if (getifaddrs(&ifaddr) != 0) {
45 unsigned int result = 0;
46 for (
auto* ifa = ifaddr; ifa !=
nullptr; ifa = ifa->ifa_next) {
47 if (!ifa->ifa_addr)
continue;
48 char host[INET6_ADDRSTRLEN] = {};
49 if (ifa->ifa_addr->sa_family == AF_INET6) {
50 auto* sin6 =
reinterpret_cast<struct sockaddr_in6*
>(ifa->ifa_addr);
51 if (inet_ntop(AF_INET6, &sin6->sin6_addr, host,
sizeof(host)) && iface_address == host) {
52 result = if_nametoindex(ifa->ifa_name);
55 }
else if (ifa->ifa_addr->sa_family == AF_INET) {
56 auto* sin =
reinterpret_cast<struct sockaddr_in*
>(ifa->ifa_addr);
57 if (inet_ntop(AF_INET, &sin->sin_addr, host,
sizeof(host)) && iface_address == host) {
58 result = if_nametoindex(ifa->ifa_name);
69 short port, uint64_t tsi,
70 boost::asio::io_context& io_context,
71 const std::string& source_address)
74 , _mcast_address(address)
82 auto mcast_address = boost::asio::ip::make_address(address);
83 bool is_v6 = mcast_address.is_v6();
94 boost::asio::ip::udp::endpoint listen_endpoint(
95 is_v6 ? boost::asio::ip::address(boost::asio::ip::address_v6::any())
96 : boost::asio::ip::address(boost::asio::ip::address_v4::any()),
98 _socket.open(listen_endpoint.protocol());
99 _socket.set_option(boost::asio::ip::multicast::enable_loopback(
true));
100 _socket.set_option(boost::asio::ip::udp::socket::reuse_address(
true));
101 _socket.set_option(boost::asio::socket_base::receive_buffer_size(16*1024*1024));
102 _socket.bind(listen_endpoint);
104 if (!source_address.empty()) {
114 struct group_source_req gsr{};
115 gsr.gsr_interface = resolve_iface_index(iface);
117 struct sockaddr_in6 grp{};
118 grp.sin6_family = AF_INET6;
119 auto mcast_bytes = mcast_address.to_v6().to_bytes();
120 std::memcpy(&grp.sin6_addr, mcast_bytes.data(), mcast_bytes.size());
121 std::memcpy(&gsr.gsr_group, &grp,
sizeof(grp));
123 struct sockaddr_in6 src{};
124 src.sin6_family = AF_INET6;
125 auto src_bytes = boost::asio::ip::make_address(source_address).to_v6().to_bytes();
126 std::memcpy(&src.sin6_addr, src_bytes.data(), src_bytes.size());
127 std::memcpy(&gsr.gsr_source, &src,
sizeof(src));
129 if (setsockopt(_socket.native_handle(), IPPROTO_IPV6, MCAST_JOIN_SOURCE_GROUP,
130 &gsr,
sizeof(gsr)) != 0) {
131 spdlog::error(
"Receiver: MCAST_JOIN_SOURCE_GROUP for {} from {} failed: {}", address,
132 source_address, strerror(errno));
134 spdlog::info(
"Receiver: joined SSM {} from source {} on iface {}", address,
135 source_address, iface);
138 struct ip_mreq_source mreq_source{};
139 auto mcast_bytes = mcast_address.to_v4().to_bytes();
140 auto src_bytes = boost::asio::ip::make_address(source_address).to_v4().to_bytes();
141 auto iface_bytes = boost::asio::ip::make_address(iface).to_v4().to_bytes();
142 std::memcpy(&mreq_source.imr_multiaddr, mcast_bytes.data(), mcast_bytes.size());
143 std::memcpy(&mreq_source.imr_sourceaddr, src_bytes.data(), src_bytes.size());
144 std::memcpy(&mreq_source.imr_interface, iface_bytes.data(), iface_bytes.size());
146 if (setsockopt(_socket.native_handle(), IPPROTO_IP, IP_ADD_SOURCE_MEMBERSHIP,
147 &mreq_source,
sizeof(mreq_source)) != 0) {
148 spdlog::error(
"Receiver: IP_ADD_SOURCE_MEMBERSHIP for {} from {} failed: {}", address,
149 source_address, strerror(errno));
151 spdlog::info(
"Receiver: joined SSM {} from source {} on iface {}", address,
152 source_address, iface);
160 boost::asio::ip::multicast::join_group(
161 mcast_address.to_v6(), resolve_iface_index(iface)));
170 boost::asio::ip::multicast::join_group(
171 mcast_address.to_v4(),
172 boost::asio::ip::make_address(iface).to_v4()));
183 auto LibFlute::Receiver::arm_receive() ->
void
186 _socket.async_receive_from(
187 boost::asio::buffer(_data, max_length), _sender_endpoint,
188 [
this, alive](
const boost::system::error_code& error,
size_t bytes_recvd) {
190 handle_receive_from(error, bytes_recvd);
199 auto LibFlute::Receiver::handle_receive_from(
const boost::system::error_code& error,
200 size_t bytes_recvd) ->
void
202 if (!_running)
return;
206 spdlog::trace(
"Received {} bytes", bytes_recvd);
210 if (alc.tsi() == _tsi) {
212 const std::lock_guard<std::mutex> lock(_files_mutex);
214 if (alc.toi() == 0 && (!_fdt || _fdt->instance_id() != alc.fdt_instance_id())) {
226 auto existing = _files.find(0);
227 if (existing == _files.end() || _fdt_in_progress_instance_id != alc.fdt_instance_id()) {
229 _files[0] = std::make_shared<LibFlute::File>(fe);
230 _fdt_in_progress_instance_id = alc.fdt_instance_id();
234 if (_files.find(alc.toi()) != _files.end() && !_files[alc.toi()]->complete()) {
236 _data + alc.header_length(),
237 bytes_recvd - alc.header_length(),
238 _files[alc.toi()]->fec_oti(),
239 alc.content_encoding());
241 for (
const auto& symbol : encoding_symbols) {
242 spdlog::debug(
"received TOI {} SBN {} ID {}", alc.toi(), symbol.source_block_number(), symbol.id() );
243 _files[alc.toi()]->put_symbol(symbol);
246 auto file = _files[alc.toi()].get();
247 if (_files[alc.toi()]->complete()) {
248 for (
auto it = _files.cbegin(); it != _files.cend();)
250 if (it->second.get() != file && it->second->meta().content_location == file->meta().content_location)
252 spdlog::debug(
"Replacing file with TOI {}", it->first);
253 it = _files.erase(it);
263 spdlog::debug(
"File with TOI {} completed", alc.toi());
264 if (alc.toi() != 0 && _completion_cb) {
265 _completion_cb(_files[alc.toi()]);
266 _files.erase(alc.toi());
269 if (alc.toi() == 0) {
270 _fdt = std::make_unique<LibFlute::FileDeliveryTable>(
271 alc.fdt_instance_id(), _files[alc.toi()]->buffer(), _files[alc.toi()]->length());
273 _files.erase(alc.toi());
274 for (
const auto& file_entry : _fdt->file_entries()) {
276 auto existing_file = _files.find(file_entry.toi);
277 if (existing_file != _files.end() &&
278 existing_file->second->meta().content_location != file_entry.content_location) {
290 spdlog::debug(
"Discarding stale incomplete file for reused TOI {} ({} != {})",
291 file_entry.toi, existing_file->second->meta().content_location, file_entry.content_location);
292 _files.erase(existing_file);
293 existing_file = _files.end();
295 if (existing_file == _files.end()) {
296 spdlog::debug(
"Starting reception for file with TOI {}: {} ({})", file_entry.toi,
297 file_entry.content_location, file_entry.content_type);
298 _files.emplace(file_entry.toi, std::make_shared<LibFlute::File>(file_entry));
304 spdlog::trace(
"Discarding packet for unknown or already completed file with TOI {}", alc.toi());
307 spdlog::warn(
"Discarding packet for unknown TSI {}", alc.tsi());
309 }
catch (
const std::exception &ex) {
310 spdlog::warn(
"Failed to decode ALC/FLUTE packet: {}", ex.what());
311 }
catch (
const char* ex) {
317 spdlog::warn(
"Failed to decode ALC/FLUTE packet: {}", ex);
324 spdlog::error(
"receive_from error: {}", error.message());
330 std::vector<std::shared_ptr<LibFlute::File>> files;
331 for (
auto& f : _files) {
332 files.push_back(f.second);
339 const std::lock_guard<std::mutex> lock(_files_mutex);
340 for (
auto it = _files.cbegin(); it != _files.cend();)
342 auto age = time(
nullptr) - it->second->received_at();
343 if ( it->second->meta().content_location !=
"bootstrap.multipart" && age > max_age) {
344 it = _files.erase(it);
353 const std::lock_guard<std::mutex> lock(_files_mutex);
354 for (
auto it = _files.cbegin(); it != _files.cend();)
356 if ( it->second->meta().content_location == cl) {
357 it = _files.erase(it);
A class for parsing and creating ALC packets.
static std::vector< EncodingSymbol > from_payload(char *encoded_data, size_t data_len, const FecOti &fec_oti, ContentEncoding encoding)
Parse and construct all encoding symbols from a payload data buffer.
Receiver(const std::string &iface, const std::string &address, short port, uint64_t tsi, boost::asio::io_context &io_context, const std::string &source_address="")
Default constructor.
void remove_file_with_content_location(const std::string &cl)
Remove a file from the list that matches the passed content location.
virtual ~Receiver()
Destructor.
std::vector< std::shared_ptr< LibFlute::File > > file_list()
List all current files.
void enable_ipsec(uint32_t spi, const std::string &aes_key)
Enable IPSEC ESP decryption of FLUTE payloads.
void remove_expired_files(unsigned max_age)
Remove files from the list that are older than max_age seconds.
void enable_esp(uint32_t spi, const std::string &dest_address, Direction direction, const std::string &key)
An entry for a file in the FDT.