/* Copyright (c) 2015-2017, Steven Siloti Copyright (c) 2007-2012, 2014-2021, Arvid Norberg Copyright (c) 2016-2018, 2021, Alden Torres All rights reserved. You may use, distribute and modify this code under the terms of the BSD license, see LICENSE file. */ #include "libtorrent/config.hpp" #include "libtorrent/aux_/disable_warnings_push.hpp" #include #include "libtorrent/aux_/disable_warnings_pop.hpp" #include "libtorrent/socket.hpp" #include "libtorrent/aux_/enum_net.hpp" #include "libtorrent/assert.hpp" #include "libtorrent/aux_/debug.hpp" #include "libtorrent/aux_/ip_helpers.hpp" #include "broadcast_socket.hpp" using namespace std::placeholders; namespace libtorrent { broadcast_socket::broadcast_socket( udp::endpoint multicast_endpoint) : m_multicast_endpoint(std::move(multicast_endpoint)) , m_outstanding_operations(0) , m_abort(false) { TORRENT_ASSERT(m_multicast_endpoint.address().is_multicast()); } void broadcast_socket::open(receive_handler_t handler , io_context& ios, error_code& ec, bool loopback) { m_on_receive = std::move(handler); std::vector interfaces = aux::enum_net_interfaces(ios, ec); if (aux::is_v6(m_multicast_endpoint)) open_multicast_socket(ios, address_v6::any(), loopback, ec); else open_multicast_socket(ios, address_v4::any(), loopback, ec); for (auto const& i : interfaces) { // only multicast on compatible networks if (i.interface_address.is_v4() != aux::is_v4(m_multicast_endpoint)) continue; // ignore any loopback interface if (!loopback && i.interface_address.is_loopback()) continue; ec = error_code(); open_multicast_socket(ios, i.interface_address, loopback, ec); open_unicast_socket(ios, i.interface_address , i.netmask.is_v4() ? i.netmask.to_v4() : address_v4()); } } void broadcast_socket::open_multicast_socket(io_context& ios , address const& addr, bool loopback, error_code& ec) { using namespace boost::asio::ip::multicast; std::shared_ptr s = std::make_shared(ios); s->open(addr.is_v4() ? udp::v4() : udp::v6(), ec); if (ec) return; s->set_option(udp::socket::reuse_address(true), ec); if (ec) return; s->bind(udp::endpoint(addr, m_multicast_endpoint.port()), ec); if (ec) return; s->set_option(join_group(m_multicast_endpoint.address()), ec); if (ec) return; s->set_option(hops(255), ec); if (ec) return; s->set_option(enable_loopback(loopback), ec); if (ec) return; m_sockets.emplace_back(s); socket_entry& se = m_sockets.back(); ADD_OUTSTANDING_ASYNC("broadcast_socket::on_receive"); s->async_receive_from(boost::asio::buffer(se.buffer) , se.remote, std::bind(&broadcast_socket::on_receive, this, &se, _1, _2)); ++m_outstanding_operations; } void broadcast_socket::open_unicast_socket(io_context& ios, address const& addr , address_v4 const& mask) { error_code ec; std::shared_ptr s = std::make_shared(ios); s->open(addr.is_v4() ? udp::v4() : udp::v6(), ec); if (ec) return; m_unicast_sockets.emplace_back(s, mask); socket_entry& se = m_unicast_sockets.back(); // allow sending broadcast messages boost::asio::socket_base::broadcast option(true); s->set_option(option, ec); if (!ec) se.broadcast = true; ADD_OUTSTANDING_ASYNC("broadcast_socket::on_receive"); s->async_receive_from(boost::asio::buffer(se.buffer) , se.remote, std::bind(&broadcast_socket::on_receive, this, &se, _1, _2)); ++m_outstanding_operations; } void broadcast_socket::send_to(char const* buffer, int size , udp::endpoint const& to, error_code& ec) { bool all_fail = true; error_code e; for (auto& s : m_sockets) { if (!s.socket) continue; s.socket->send_to(boost::asio::buffer(buffer, std::size_t(size)), to, 0, e); if (e) { s.socket->close(e); s.socket.reset(); } else { all_fail = false; } } if (all_fail) ec = e; } void broadcast_socket::send(char const* buffer, int const size , error_code& ec, int const flags) { bool all_fail = true; error_code e; for (auto& s : m_unicast_sockets) { if (!s.socket) continue; s.socket->send_to(boost::asio::buffer(buffer, std::size_t(size)), m_multicast_endpoint, 0, e); // if the user specified the broadcast flag, send one to the broadcast // address as well if ((flags & broadcast_socket::flag_broadcast) && s.can_broadcast()) s.socket->send_to(boost::asio::buffer(buffer, std::size_t(size)) , udp::endpoint(s.broadcast_address(), m_multicast_endpoint.port()), 0, e); if (e) { s.socket->close(e); s.socket.reset(); } else { all_fail = false; } } for (auto& s : m_sockets) { if (!s.socket) continue; s.socket->send_to(boost::asio::buffer(buffer, std::size_t(size)), m_multicast_endpoint, 0, e); if (e) { s.socket->close(e); s.socket.reset(); } else { all_fail = false; } } if (all_fail) ec = e; } void broadcast_socket::on_receive(socket_entry* s, error_code const& ec , std::size_t bytes_transferred) { COMPLETE_ASYNC("broadcast_socket::on_receive"); TORRENT_ASSERT(m_outstanding_operations > 0); --m_outstanding_operations; if (ec || bytes_transferred == 0 || !m_on_receive) { maybe_abort(); return; } m_on_receive(s->remote, {s->buffer.data(), int(bytes_transferred)}); if (maybe_abort()) return; if (!s->socket) return; ADD_OUTSTANDING_ASYNC("broadcast_socket::on_receive"); s->socket->async_receive_from(boost::asio::buffer(s->buffer) , s->remote, std::bind(&broadcast_socket::on_receive, this, s, _1, _2)); ++m_outstanding_operations; } bool broadcast_socket::maybe_abort() { bool ret = m_abort; if (m_abort && m_outstanding_operations == 0) { // it's important that m_on_receive is cleared // before the object is destructed, since it may // hold a reference to ourself, which would otherwise // cause an infinite recursion destructing the objects receive_handler_t().swap(m_on_receive); } return ret; } void broadcast_socket::close() { std::for_each(m_sockets.begin(), m_sockets.end(), std::bind(&socket_entry::close, _1)); std::for_each(m_unicast_sockets.begin(), m_unicast_sockets.end(), std::bind(&socket_entry::close, _1)); m_abort = true; maybe_abort(); } }