libflute
Public Types | Public Member Functions | List of all members
LibFlute::Receiver Class Reference

FLUTE receiver class. More...

#include <Receiver.h>

Public Types

typedef std::function< void(std::shared_ptr< LibFlute::File >)> completion_callback_t
 Definition of a file reception completion callback function that can be registered through ::register_completion_callback. More...
 

Public Member Functions

 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. More...
 
virtual ~Receiver ()
 Destructor. More...
 
void enable_ipsec (uint32_t spi, const std::string &aes_key)
 Enable IPSEC ESP decryption of FLUTE payloads. More...
 
std::vector< std::shared_ptr< LibFlute::File > > file_list ()
 List all current files. More...
 
void remove_expired_files (unsigned max_age)
 Remove files from the list that are older than max_age seconds. More...
 
void remove_file_with_content_location (const std::string &cl)
 Remove a file from the list that matches the passed content location. More...
 
void register_completion_callback (completion_callback_t cb)
 Register a callback for file reception notifications. More...
 
void stop ()
 

Detailed Description

FLUTE receiver class.

Construct an instance of this to receive files from a FLUTE/ALC session.

Definition at line 31 of file Receiver.h.

Member Typedef Documentation

◆ completion_callback_t

typedef std::function<void(std::shared_ptr<LibFlute::File>)> LibFlute::Receiver::completion_callback_t

Definition of a file reception completion callback function that can be registered through ::register_completion_callback.

Returns
shared_ptr to the received file

Definition at line 39 of file Receiver.h.

Constructor & Destructor Documentation

◆ Receiver()

LibFlute::Receiver::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.

Parameters
ifaceAddress of the (local) interface to bind the receiving socket to. 0.0.0.0 = any.
addressMulticast address
portTarget port
tsiTSI value of the session
io_contextBoost io_context to run the socket operations in (must be provided by the caller)
source_addressIf non-empty, join as source-specific multicast (SSM, IPv4 only) admitting only packets from this source – otherwise ASM (any-source).

Definition at line 68 of file Receiver.cpp.

72  : _socket(io_context)
73  , _tsi(tsi)
74  , _mcast_address(address)
75 {
76  // Restored alongside the ANY-bind/specific-interface-join fixes below:
77  // an earlier version of those fixes made this whole constructor IPv4
78  // only, where the original code let Boost pick v4 vs v6 based on the
79  // address types passed in. `address`'s family (not `iface`'s -- iface
80  // can legitimately be "0.0.0.0"/"::"/empty, meaning "any") is the
81  // authoritative signal for which family this session actually uses.
82  auto mcast_address = boost::asio::ip::make_address(address);
83  bool is_v6 = mcast_address.is_v6();
84 
85  // Bind to ANY, not the specific interface address: incoming multicast
86  // packets are addressed to the group, not to a particular unicast
87  // interface address, so binding to that unicast address is non-standard
88  // - and in practice it also breaks epoll-based readiness notification
89  // for this socket (confirmed directly: async_receive_from never
90  // completes when bound to a specific interface address, even though the
91  // data really does arrive and a plain synchronous recv() picks it up;
92  // binding to ANY fixes it). `iface` is still used below to select which
93  // interface's multicast membership to join.
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()),
97  port);
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);
103 
104  if (!source_address.empty()) {
105  // Source-specific multicast (SSM, RFC 4607): admits only packets from source_address,
106  // as indicated by an SDP a=source-filter line (RFC 4570; TS 26.517 cl.6.2.2.3's own
107  // examples use this for FLUTE sessions). boost::asio has no portable SSM join, so this
108  // goes straight to the socket options Linux (and most other stacks) actually define --
109  // IPv4's ip_mreq_source/IP_ADD_SOURCE_MEMBERSHIP identifies the interface by address;
110  // IPv6's group_source_req/MCAST_JOIN_SOURCE_GROUP identifies it by index instead
111  // (see resolve_iface_index() above), which is why the two branches build genuinely
112  // different structures rather than sharing one.
113  if (is_v6) {
114  struct group_source_req gsr{};
115  gsr.gsr_interface = resolve_iface_index(iface);
116 
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));
122 
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));
128 
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));
133  } else {
134  spdlog::info("Receiver: joined SSM {} from source {} on iface {}", address,
135  source_address, iface);
136  }
137  } else {
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());
145 
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));
150  } else {
151  spdlog::info("Receiver: joined SSM {} from source {} on iface {}", address,
152  source_address, iface);
153  }
154  }
155  } else if (is_v6) {
156  // Plain (any-source) multicast join, IPv6: join_group(address_v6, interface_index) --
157  // a different overload shape than IPv4's join_group(address_v4, address_v4) below,
158  // since v6 identifies the interface by index (see resolve_iface_index()).
159  _socket.set_option(
160  boost::asio::ip::multicast::join_group(
161  mcast_address.to_v6(), resolve_iface_index(iface)));
162  } else {
163  // Join the multicast group on the specific interface passed in - the
164  // single-address join_group() overload ignores `iface` entirely and
165  // joins via whatever interface the system considers the default route
166  // for the group, which silently breaks reception when the content
167  // actually arrives on a non-default interface (e.g. the modem's own
168  // TUN device rather than a physical NIC).
169  _socket.set_option(
170  boost::asio::ip::multicast::join_group(
171  mcast_address.to_v4(),
172  boost::asio::ip::make_address(iface).to_v4()));
173  }
174 
175  arm_receive();
176 }

◆ ~Receiver()

LibFlute::Receiver::~Receiver ( )
virtual

Destructor.

Marks the receiver as no longer alive so that any async_receive_from completion already queued on the io_context when this object is destroyed (e.g. the owner recreated the receiver, or a caller destroys it mid-flight) finds out before touching a freed this – boost::asio only guarantees a cancelled operation's handler eventually runs with operation_aborted, not that it runs before the destructor returns, so a raw this-bound handler left queued past that point would otherwise be a use-after-free.

Definition at line 178 of file Receiver.cpp.

179 {
180  *_alive = false;
181 }

Member Function Documentation

◆ enable_ipsec()

auto LibFlute::Receiver::enable_ipsec ( uint32_t  spi,
const std::string &  aes_key 
)

Enable IPSEC ESP decryption of FLUTE payloads.

Parameters
spiSecurity Parameter Index value to use
keyAES key as a hex string (without leading 0x). Must be an even number of characters long.

Definition at line 194 of file Receiver.cpp.

195 {
197 }
void enable_esp(uint32_t spi, const std::string &dest_address, Direction direction, const std::string &key)
Definition: IpSec.cpp:122

◆ file_list()

auto LibFlute::Receiver::file_list ( )

List all current files.

Returns
Vector of all files currently in the FDT

Definition at line 328 of file Receiver.cpp.

329 {
330  std::vector<std::shared_ptr<LibFlute::File>> files;
331  for (auto& f : _files) {
332  files.push_back(f.second);
333  }
334  return files;
335 }

◆ register_completion_callback()

void LibFlute::Receiver::register_completion_callback ( completion_callback_t  cb)
inline

Register a callback for file reception notifications.

Parameters
cbFunction to call on file completion

Definition at line 97 of file Receiver.h.

97 { _completion_cb = cb; };

◆ remove_expired_files()

auto LibFlute::Receiver::remove_expired_files ( unsigned  max_age)

Remove files from the list that are older than max_age seconds.

Definition at line 337 of file Receiver.cpp.

338 {
339  const std::lock_guard<std::mutex> lock(_files_mutex);
340  for (auto it = _files.cbegin(); it != _files.cend();)
341  {
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);
345  } else {
346  ++it;
347  }
348  }
349 }

◆ remove_file_with_content_location()

auto LibFlute::Receiver::remove_file_with_content_location ( const std::string &  cl)

Remove a file from the list that matches the passed content location.

Definition at line 351 of file Receiver.cpp.

352 {
353  const std::lock_guard<std::mutex> lock(_files_mutex);
354  for (auto it = _files.cbegin(); it != _files.cend();)
355  {
356  if ( it->second->meta().content_location == cl) {
357  it = _files.erase(it);
358  } else {
359  ++it;
360  }
361  }
362 }

◆ stop()

void LibFlute::Receiver::stop ( )
inline

Definition at line 99 of file Receiver.h.

99 { _running = false; }

The documentation for this class was generated from the following files: