Compare commits

...

10 Commits

Author SHA1 Message Date
Yat Ho 3cc9711ab4 refactor: rename `mcast_socket_` to `mcast_sockets_` 2024-04-24 10:42:59 +08:00
Yat Ho 5e8ab62de5 refactor: dedupe `if_nametoindex()` call 2024-04-24 10:42:59 +08:00
Yat Ho 8de4269047 chore: housekeeping 2024-04-24 10:42:59 +08:00
Yat Ho 83d2dc991d fix: enable multicast loop 2024-04-24 10:42:59 +08:00
Yat Ho 3fe87979a1 refactor: use `tr_socket_address::from_string()` 2024-04-24 10:42:59 +08:00
Yat Ho b4ad1f78fc feat: find interface index from ip address 2024-04-24 10:42:59 +08:00
Yat Ho 9bcb86ebb0 feat: ipv6 lpd 2024-04-24 10:42:59 +08:00
Pooyan Khanjankhani 821a6816ef
doc: fix typo (#6790) 2024-04-21 18:21:17 -05:00
Dzmitry Neviadomski ef18816b7f
Fix code style script path in CONTRIBUTING.md (#6787)
Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>
2024-04-21 07:36:13 -05:00
Dzmitry Neviadomski 0e25584e78
Make std::hash specialization for tr_socket_address a struct (#6788)
To be in line with std::hash declaration

See https://en.cppreference.com/w/cpp/utility/hash

Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>
2024-04-20 21:01:47 -05:00
6 changed files with 348 additions and 107 deletions

View File

@ -41,7 +41,7 @@ On macOS, Transmission is usually built with Xcode. Everywhere else, it's CMake
- Prefer `enum class` over `enum` - Prefer `enum class` over `enum`
- Prefer new-style headers, e.g. `<cstring>` over `<string.h>` - Prefer new-style headers, e.g. `<cstring>` over `<string.h>`
- Fix any warnings in new code before merging - Fix any warnings in new code before merging
- Run `./code-style.sh` on your code to ensure the whole codebase has consistent indentation. - Run `./code_style.sh` on your code to ensure the whole codebase has consistent indentation.
Note that Transmission existed in C for over a decade and those idioms don't change overnight. "Follow the C++ core guidelines" can be difficult when working with older code, and the maintainers will understand that when reviewing your PRs. :smiley: Note that Transmission existed in C for over a decade and those idioms don't change overnight. "Follow the C++ core guidelines" can be difficult when working with older code, and the maintainers will understand that when reviewing your PRs. :smiley:

View File

@ -16,8 +16,12 @@
#include <utility> // std::pair #include <utility> // std::pair
#ifdef _WIN32 #ifdef _WIN32
#include <winsock2.h> // must come before iphlpapi.h
#include <iphlpapi.h>
#include <ws2tcpip.h> #include <ws2tcpip.h>
#else #else
#include <ifaddrs.h>
#include <net/if.h>
#include <netinet/tcp.h> /* TCP_CONGESTION */ #include <netinet/tcp.h> /* TCP_CONGESTION */
#endif #endif
@ -556,6 +560,92 @@ std::pair<tr_address, std::byte const*> tr_address::from_compact_ipv6(std::byte
return { address, compact }; return { address, compact };
} }
std::optional<unsigned> tr_address::to_interface_index() const noexcept
{
if (!is_valid())
{
tr_logAddDebug("Invalid target address to find interface index");
return {};
}
tr_logAddDebug(fmt::format("Find interface index for {}", display_name()));
#ifdef _WIN32
auto p_addresses = std::unique_ptr<void, void (*)(void*)>{ nullptr, operator delete };
for (auto p_addesses_size = ULONG{ 15000 } /* 15KB */;;)
{
p_addresses.reset(operator new(p_addesses_size, std::nothrow));
if (!p_addresses)
{
tr_logAddDebug("Could not allocate memory for interface list");
return {};
}
if (auto ret = GetAdaptersAddresses(
AF_UNSPEC,
GAA_FLAG_SKIP_FRIENDLY_NAME,
nullptr,
reinterpret_cast<PIP_ADAPTER_ADDRESSES>(p_addresses.get()),
&p_addesses_size);
ret != ERROR_BUFFER_OVERFLOW)
{
if (ret != ERROR_SUCCESS)
{
tr_logAddDebug(fmt::format("Failed to retrieve interface list: {} ({})", ret, tr_win32_format_message(ret)));
return {};
}
break;
}
}
for (auto const* cur = reinterpret_cast<PIP_ADAPTER_ADDRESSES>(p_addresses.get()); cur != nullptr; cur = cur->Next)
{
if (cur->OperStatus != IfOperStatusUp)
{
continue;
}
for (auto const* sa_p = cur->FirstUnicastAddress; sa_p != nullptr; sa_p = sa_p->Next)
{
if (auto if_addr = tr_socket_address::from_sockaddr(sa_p->Address.lpSockaddr);
if_addr && if_addr->address() == *this)
{
auto const ret = type == TR_AF_INET ? cur->IfIndex : cur->Ipv6IfIndex;
tr_logAddDebug(fmt::format("Found interface index for {}: {}", display_name(), ret));
return ret;
}
}
}
#else
struct ifaddrs* ifa = nullptr;
if (getifaddrs(&ifa) != 0)
{
auto err = errno;
tr_logAddDebug(fmt::format("Failed to retrieve interface list: {} ({})", err, tr_strerror(err)));
return {};
}
auto const ifa_uniq = std::unique_ptr<ifaddrs, void (*)(struct ifaddrs*)>{ ifa, freeifaddrs };
for (; ifa != nullptr; ifa = ifa->ifa_next)
{
if (ifa->ifa_addr == nullptr || (ifa->ifa_flags & IFF_UP) == 0U)
{
continue;
}
if (auto if_addr = tr_socket_address::from_sockaddr(ifa->ifa_addr); if_addr && if_addr->address() == *this)
{
auto const ret = if_nametoindex(ifa->ifa_name);
tr_logAddDebug(fmt::format("Found interface index for {}: {}", display_name(), ret));
return ret;
}
}
#endif
tr_logAddDebug(fmt::format("Could not find interface index for {}", display_name()));
return {};
}
int tr_address::compare(tr_address const& that) const noexcept // <=> int tr_address::compare(tr_address const& that) const noexcept // <=>
{ {
// IPv6 addresses are always "greater than" IPv4 // IPv6 addresses are always "greater than" IPv4
@ -718,6 +808,19 @@ bool tr_socket_address::is_valid_for_peers(tr_peer_from from) const noexcept
!is_martian_addr(address_, from); !is_martian_addr(address_, from);
} }
std::optional<tr_socket_address> tr_socket_address::from_string(std::string_view sockaddr_sv)
{
auto ss = sockaddr_storage{};
auto sslen = int{ sizeof(ss) };
if (evutil_parse_sockaddr_port(tr_strbuf<char, TR_ADDRSTRLEN>{ sockaddr_sv }, reinterpret_cast<sockaddr*>(&ss), &sslen) !=
0)
{
return {};
}
return from_sockaddr(reinterpret_cast<struct sockaddr const*>(&ss));
}
std::optional<tr_socket_address> tr_socket_address::from_sockaddr(struct sockaddr const* from) std::optional<tr_socket_address> tr_socket_address::from_sockaddr(struct sockaddr const* from)
{ {
if (from == nullptr) if (from == nullptr)

View File

@ -161,11 +161,11 @@ struct tr_address
[[nodiscard]] static std::pair<tr_address, std::byte const*> from_compact_ipv4(std::byte const* compact) noexcept; [[nodiscard]] static std::pair<tr_address, std::byte const*> from_compact_ipv4(std::byte const* compact) noexcept;
[[nodiscard]] static std::pair<tr_address, std::byte const*> from_compact_ipv6(std::byte const* compact) noexcept; [[nodiscard]] static std::pair<tr_address, std::byte const*> from_compact_ipv6(std::byte const* compact) noexcept;
// write the text form of the address, e.g. inet_ntop() // --- write the text form of the address, e.g. inet_ntop()
std::string_view display_name(char* out, size_t outlen) const; std::string_view display_name(char* out, size_t outlen) const;
[[nodiscard]] std::string display_name() const; [[nodiscard]] std::string display_name() const;
/// // ---
[[nodiscard]] constexpr auto is_ipv4() const noexcept [[nodiscard]] constexpr auto is_ipv4() const noexcept
{ {
@ -177,7 +177,7 @@ struct tr_address
return type == TR_AF_INET6; return type == TR_AF_INET6;
} }
/// bt protocol compact form // --- bt protocol compact form
// compact addr only -- used e.g. as `yourip` value in extension protocol handshake // compact addr only -- used e.g. as `yourip` value in extension protocol handshake
@ -208,7 +208,11 @@ struct tr_address
} }
} }
// comparisons // ---
[[nodiscard]] std::optional<unsigned> to_interface_index() const noexcept;
// --- comparisons
[[nodiscard]] int compare(tr_address const& that) const noexcept; [[nodiscard]] int compare(tr_address const& that) const noexcept;
@ -232,7 +236,7 @@ struct tr_address
return this->compare(that) > 0; return this->compare(that) > 0;
} }
// // ---
[[nodiscard]] bool is_global_unicast_address() const noexcept; [[nodiscard]] bool is_global_unicast_address() const noexcept;
@ -374,6 +378,7 @@ struct tr_socket_address
// --- sockaddr helpers // --- sockaddr helpers
[[nodiscard]] static std::optional<tr_socket_address> from_string(std::string_view sockaddr_sv);
[[nodiscard]] static std::optional<tr_socket_address> from_sockaddr(sockaddr const*); [[nodiscard]] static std::optional<tr_socket_address> from_sockaddr(sockaddr const*);
[[nodiscard]] static std::pair<sockaddr_storage, socklen_t> to_sockaddr(tr_address const& addr, tr_port port) noexcept; [[nodiscard]] static std::pair<sockaddr_storage, socklen_t> to_sockaddr(tr_address const& addr, tr_port port) noexcept;
@ -404,7 +409,7 @@ struct tr_socket_address
}; };
template<> template<>
class std::hash<tr_socket_address> struct std::hash<tr_socket_address>
{ {
public: public:
std::size_t operator()(tr_socket_address const& socket_address) const noexcept std::size_t operator()(tr_socket_address const& socket_address) const noexcept

View File

@ -14,13 +14,14 @@
#include <optional> #include <optional>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <type_traits>
#include <vector> #include <vector>
#ifdef _WIN32 #ifdef _WIN32
#include <ws2tcpip.h> #include <ws2tcpip.h>
#else #else
#include <sys/socket.h> /* socket(), bind() */
#include <netinet/in.h> /* sockaddr_in */ #include <netinet/in.h> /* sockaddr_in */
#include <sys/socket.h> /* socket(), bind() */
#endif #endif
#include <event2/event.h> #include <event2/event.h>
@ -47,6 +48,8 @@ using namespace std::literals;
namespace namespace
{ {
using ipp_t = std::underlying_type_t<tr_address_type>;
// opaque value, allowing the sending client to filter out its // opaque value, allowing the sending client to filter out its
// own announces if it receives them via multicast loopback // own announces if it receives them via multicast loopback
auto makeCookie() auto makeCookie()
@ -62,8 +65,8 @@ auto makeCookie()
return std::string{ std::data(buf), std::size(buf) }; return std::string{ std::data(buf), std::size(buf) };
} }
constexpr char const* const McastGroup = "239.192.152.143"; /**<LPD multicast group */ auto constexpr McastSockAddr = std::array{ "239.192.152.143:6771"sv, "[ff15::efc0:988f]:6771"sv };
auto constexpr McastPort = tr_port::from_host(6771); /**<LPD source and destination UPD port */ static_assert(std::size(McastSockAddr) == NUM_TR_AF_INET_TYPES);
/* /*
* A LSD announce is formatted as follows: * A LSD announce is formatted as follows:
@ -84,14 +87,23 @@ auto constexpr McastPort = tr_port::from_host(6771); /**<LPD source and destinat
* multiple infohashes the packet length should not exceed 1400 * multiple infohashes the packet length should not exceed 1400
* bytes to avoid MTU/fragmentation problems. * bytes to avoid MTU/fragmentation problems.
*/ */
auto makeAnnounceMsg(std::string_view cookie, tr_port port, std::vector<std::string_view> const& info_hash_strings) std::string makeAnnounceMsg(
tr_address_type ip_protocol,
std::string_view cookie,
tr_port port,
std::vector<std::string_view> const& info_hash_strings)
{ {
TR_ASSERT(tr_address::is_valid(ip_protocol));
if (!tr_address::is_valid(ip_protocol))
{
return {};
}
auto ret = fmt::format( auto ret = fmt::format(
"BT-SEARCH * HTTP/1.1\r\n" "BT-SEARCH * HTTP/1.1\r\n"
"Host: {:s}:{:d}\r\n" "Host: {:s}\r\n"
"Port: {:d}\r\n", "Port: {:d}\r\n",
McastGroup, McastSockAddr[ip_protocol],
McastPort.host(),
port.host()); port.host());
for (auto const& info_hash : info_hash_strings) for (auto const& info_hash : info_hash_strings)
@ -230,11 +242,17 @@ public:
~tr_lpd_impl() override ~tr_lpd_impl() override
{ {
event_.reset(); for (auto& event : events_)
if (mcast_socket_ != TR_BAD_SOCKET)
{ {
tr_net_close_socket(mcast_socket_); event.reset();
}
for (auto const sock : mcast_sockets_)
{
if (sock != TR_BAD_SOCKET)
{
tr_net_close_socket(sock);
}
} }
tr_logAddTrace("Done uninitialising Local Peer Discovery"); tr_logAddTrace("Done uninitialising Local Peer Discovery");
@ -243,19 +261,34 @@ public:
private: private:
bool init(struct event_base* event_base) bool init(struct event_base* event_base)
{ {
if (initImpl(event_base)) ipp_t n_success = NUM_TR_AF_INET_TYPES;
if (!initImpl<TR_AF_INET>(event_base))
{ {
return true; auto const err = sockerrno;
tr_net_close_socket(mcast_sockets_[TR_AF_INET]);
mcast_sockets_[TR_AF_INET] = TR_BAD_SOCKET;
tr_logAddWarn(fmt::format(
_("Couldn't initialize {ip_protocol} LPD: {error} ({error_code})"),
fmt::arg("ip_protocol", tr_ip_protocol_to_sv(TR_AF_INET)),
fmt::arg("error", tr_strerror(err)),
fmt::arg("error_code", err)));
--n_success;
} }
auto const err = sockerrno; if (!initImpl<TR_AF_INET6>(event_base))
tr_net_close_socket(mcast_socket_); {
mcast_socket_ = TR_BAD_SOCKET; auto const err = sockerrno;
tr_logAddWarn(fmt::format( tr_net_close_socket(mcast_sockets_[TR_AF_INET6]);
_("Couldn't initialize LPD: {error} ({error_code})"), mcast_sockets_[TR_AF_INET6] = TR_BAD_SOCKET;
fmt::arg("error", tr_strerror(err)), tr_logAddWarn(fmt::format(
fmt::arg("error_code", err))); _("Couldn't initialize {ip_protocol} LPD: {error} ({error_code})"),
return false; fmt::arg("ip_protocol", tr_ip_protocol_to_sv(TR_AF_INET6)),
fmt::arg("error", tr_strerror(err)),
fmt::arg("error_code", err)));
--n_success;
}
return n_success != 0U;
} }
/** /**
@ -263,76 +296,80 @@ private:
* *
* For the most part, this means setting up an appropriately configured multicast socket * For the most part, this means setting up an appropriately configured multicast socket
* and event-based message handling. * and event-based message handling.
*
* @remark Since the LPD service does not use another protocol family yet, this code is
* IPv4 only for the time being.
*/ */
template<tr_address_type ip_protocol>
bool initImpl(struct event_base* event_base) bool initImpl(struct event_base* event_base)
{ {
auto const opt_on = 1; auto const opt_on = 1;
auto& sock = mcast_sockets_[ip_protocol];
static_assert(AnnounceScope > 0); static_assert(AnnounceScope > 0);
static_assert(tr_address::is_valid(ip_protocol));
tr_logAddDebug("Initialising Local Peer Discovery"); tr_logAddDebug(fmt::format("Initialising {} Local Peer Discovery", tr_ip_protocol_to_sv(ip_protocol)));
/* setup datagram socket */ // setup datagram socket
sock = socket(tr_ip_protocol_to_af(ip_protocol), SOCK_DGRAM, 0);
if (sock == TR_BAD_SOCKET)
{ {
mcast_socket_ = socket(PF_INET, SOCK_DGRAM, 0); return false;
}
if (mcast_socket_ == TR_BAD_SOCKET) if (evutil_make_socket_nonblocking(sock) == -1)
{ {
return false; return false;
} }
if (evutil_make_socket_nonblocking(mcast_socket_) == -1) if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char const*>(&opt_on), sizeof(opt_on)) == -1)
{ {
return false; return false;
} }
if (setsockopt(mcast_socket_, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char const*>(&opt_on), sizeof(opt_on)) ==
-1)
{
return false;
}
#if HAVE_SO_REUSEPORT #if HAVE_SO_REUSEPORT
if (setsockopt(mcast_socket_, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast<char const*>(&opt_on), sizeof(opt_on)) == if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast<char const*>(&opt_on), sizeof(opt_on)) == -1)
{
return false;
}
#endif
if constexpr (ip_protocol == TR_AF_INET6)
{
// must be done before binding on Linux
if (evutil_make_listen_socket_ipv6only(sock) == -1)
{
return false;
}
}
auto const mcast_sockaddr = tr_socket_address::from_string(McastSockAddr[ip_protocol]);
TR_ASSERT(mcast_sockaddr);
auto const [mcast_ss, mcast_sslen] = mcast_sockaddr->to_sockaddr();
auto const [bind_ss, bind_sslen] = tr_socket_address::to_sockaddr(tr_address::any(ip_protocol), mcast_sockaddr->port());
if (bind(sock, reinterpret_cast<sockaddr const*>(&bind_ss), bind_sslen) == -1)
{
return false;
}
if constexpr (ip_protocol == TR_AF_INET)
{
std::memcpy(&mcast_addr_, &mcast_ss, mcast_sslen);
// we want to join that LPD multicast group
struct ip_mreq mcast_req = {};
mcast_req.imr_multiaddr = mcast_addr_.sin_addr;
mcast_req.imr_interface = mediator_.bind_address(ip_protocol).addr.addr4;
if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, reinterpret_cast<char const*>(&mcast_req), sizeof(mcast_req)) ==
-1) -1)
{ {
return false; return false;
} }
#endif
auto const [bind_ss, bind_sslen] = tr_socket_address::to_sockaddr(mediator_.bind_address(TR_AF_INET), McastPort);
if (bind(mcast_socket_, reinterpret_cast<sockaddr const*>(&bind_ss), bind_sslen) == -1)
{
return false;
}
auto const mcast_addr = tr_address::from_string(McastGroup);
TR_ASSERT(mcast_addr);
auto const [mcast_ss, mcast_sslen] = tr_socket_address::to_sockaddr(*mcast_addr, McastPort);
std::memcpy(&mcast_addr_, &mcast_ss, mcast_sslen);
/* we want to join that LPD multicast group */
ip_mreq mcast_req = {};
mcast_req.imr_multiaddr = mcast_addr_.sin_addr;
mcast_req.imr_interface = reinterpret_cast<sockaddr_in const*>(&bind_ss)->sin_addr;
// configure outbound multicast TTL
if (setsockopt( if (setsockopt(
mcast_socket_, sock,
IPPROTO_IP,
IP_ADD_MEMBERSHIP,
reinterpret_cast<char const*>(&mcast_req),
sizeof(mcast_req)) == -1)
{
return false;
}
/* configure outbound multicast TTL */
if (setsockopt(
mcast_socket_,
IPPROTO_IP, IPPROTO_IP,
IP_MULTICAST_TTL, IP_MULTICAST_TTL,
reinterpret_cast<char const*>(&AnnounceScope), reinterpret_cast<char const*>(&AnnounceScope),
@ -340,12 +377,71 @@ private:
{ {
return false; return false;
} }
if (setsockopt(
sock,
IPPROTO_IP,
IP_MULTICAST_IF,
reinterpret_cast<char const*>(&mcast_req.imr_interface),
sizeof(mcast_req.imr_interface)) == -1)
{
return false;
}
// needed to announce to BT clients on the same interface
if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, reinterpret_cast<char const*>(&opt_on), sizeof(opt_on)) == -1)
{
return false;
}
}
else // TR_AF_INET6
{
std::memcpy(&mcast6_addr_, &mcast_ss, mcast_sslen);
// we want to join that LPD multicast group
struct ipv6_mreq mcast_req = {};
mcast_req.ipv6mr_multiaddr = mcast6_addr_.sin6_addr;
mcast_req.ipv6mr_interface = mediator_.bind_address(ip_protocol).to_interface_index().value_or(0);
if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, reinterpret_cast<char const*>(&mcast_req), sizeof(mcast_req)) ==
-1)
{
return false;
}
// configure outbound multicast TTL
if (setsockopt(
sock,
IPPROTO_IPV6,
IPV6_MULTICAST_HOPS,
reinterpret_cast<char const*>(&AnnounceScope),
sizeof(AnnounceScope)) == -1)
{
return false;
}
if (setsockopt(
sock,
IPPROTO_IPV6,
IPV6_MULTICAST_IF,
reinterpret_cast<char const*>(&mcast_req.ipv6mr_interface),
sizeof(mcast_req.ipv6mr_interface)) == -1)
{
return false;
}
// needed to announce to BT clients on the same interface
if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, reinterpret_cast<char const*>(&opt_on), sizeof(opt_on)) ==
-1)
{
return false;
}
} }
event_.reset(event_new(event_base, mcast_socket_, EV_READ | EV_PERSIST, event_callback, this)); events_[ip_protocol].reset(event_new(event_base, sock, EV_READ | EV_PERSIST, event_callback<ip_protocol>, this));
event_add(event_.get(), nullptr); event_add(events_[ip_protocol].get(), nullptr);
tr_logAddDebug("Local Peer Discovery initialised"); tr_logAddDebug(fmt::format("{} Local Peer Discovery initialised", tr_ip_protocol_to_sv(ip_protocol)));
return true; return true;
} }
@ -354,27 +450,34 @@ private:
* @brief Processing of timeout notifications and incoming data on the socket * @brief Processing of timeout notifications and incoming data on the socket
* @note maximum rate of read events is limited according to @a lpd_maxAnnounceCap * @note maximum rate of read events is limited according to @a lpd_maxAnnounceCap
* @see DoS */ * @see DoS */
template<tr_address_type ip_protocol>
static void event_callback(evutil_socket_t /*s*/, short type, void* vself) static void event_callback(evutil_socket_t /*s*/, short type, void* vself)
{ {
if ((type & EV_READ) != 0) if ((type & EV_READ) != 0)
{ {
static_cast<tr_lpd_impl*>(vself)->onCanRead(); static_cast<tr_lpd_impl*>(vself)->onCanRead(ip_protocol);
} }
} }
void onCanRead() void onCanRead(tr_address_type ip_protocol)
{ {
TR_ASSERT(tr_address::is_valid(ip_protocol));
if (!tr_address::is_valid(ip_protocol))
{
return;
}
if (!mediator_.allowsLPD()) if (!mediator_.allowsLPD())
{ {
return; return;
} }
// process announcement from foreign peer // process announcement from foreign peer
struct sockaddr_in foreign_addr = {}; struct sockaddr_storage foreign_addr = {};
auto addr_len = socklen_t{ sizeof(foreign_addr) }; auto addr_len = socklen_t{ sizeof(foreign_addr) };
auto foreign_msg = std::array<char, MaxDatagramLength>{}; auto foreign_msg = std::array<char, MaxDatagramLength>{};
auto const res = recvfrom( auto const res = recvfrom(
mcast_socket_, mcast_sockets_[ip_protocol],
std::data(foreign_msg), std::data(foreign_msg),
MaxDatagramLength, MaxDatagramLength,
0, 0,
@ -386,6 +489,7 @@ private:
{ {
return; return;
} }
TR_ASSERT(tr_af_to_ip_protocol(foreign_addr.ss_family) == ip_protocol);
// If it doesn't look like a BEP14 message, discard it // If it doesn't look like a BEP14 message, discard it
auto const msg = std::string_view{ std::data(foreign_msg), static_cast<size_t>(res) }; auto const msg = std::string_view{ std::data(foreign_msg), static_cast<size_t>(res) };
@ -410,10 +514,14 @@ private:
return; return;
} }
auto [peer_addr, compact] = tr_address::from_compact_ipv4(reinterpret_cast<std::byte*>(&foreign_addr.sin_addr)); auto peer_sockaddr = tr_socket_address::from_sockaddr(reinterpret_cast<sockaddr*>(&foreign_addr));
if (!peer_sockaddr)
{
return;
}
for (auto const& hash_string : parsed->info_hash_strings) for (auto const& hash_string : parsed->info_hash_strings)
{ {
if (!mediator_.onPeerFound(hash_string, peer_addr, parsed->port)) if (!mediator_.onPeerFound(hash_string, peer_sockaddr->address(), parsed->port))
{ {
tr_logAddDebug(fmt::format("Cannot serve torrent #{:s}", hash_string)); tr_logAddDebug(fmt::format("Cannot serve torrent #{:s}", hash_string));
} }
@ -462,19 +570,30 @@ private:
std::sort(std::begin(torrents), std::end(torrents), TorrentComparator); std::sort(std::begin(torrents), std::end(torrents), TorrentComparator);
// cram in as many as will fit in a message // cram in as many as will fit in a message
auto const baseline_size = std::size(makeAnnounceMsg(cookie_, mediator_.port(), {})); auto baseline_size = size_t{};
auto const size_with_one = std::size(makeAnnounceMsg(cookie_, mediator_.port(), { torrents.front().info_hash_str })); for (ipp_t ipp = 0; ipp < NUM_TR_AF_INET_TYPES; ++ipp)
auto const size_per_hash = size_with_one - baseline_size; {
baseline_size = std::max(
baseline_size,
std::size(makeAnnounceMsg(static_cast<tr_address_type>(ipp), cookie_, mediator_.port(), {})));
}
auto const size_per_hash = std::size(torrents.front().info_hash_str);
auto const max_torrents_per_announce = (MaxDatagramLength - baseline_size) / size_per_hash; auto const max_torrents_per_announce = (MaxDatagramLength - baseline_size) / size_per_hash;
auto const torrents_this_announce = std::min(std::size(torrents), max_torrents_per_announce);
auto info_hash_strings = std::vector<std::string_view>{}; auto info_hash_strings = std::vector<std::string_view>{};
info_hash_strings.resize(std::min(std::size(torrents), max_torrents_per_announce)); info_hash_strings.reserve(torrents_this_announce);
std::transform( std::transform(
std::begin(torrents), std::begin(torrents),
std::begin(torrents) + std::size(info_hash_strings), std::begin(torrents) + torrents_this_announce,
std::begin(info_hash_strings), std::back_inserter(info_hash_strings),
[](auto const& tor) { return tor.info_hash_str; }); [](auto const& tor) { return tor.info_hash_str; });
if (!sendAnnounce(info_hash_strings)) auto success = false;
for (ipp_t ipp = 0; ipp < NUM_TR_AF_INET_TYPES; ++ipp)
{
success |= sendAnnounce(static_cast<tr_address_type>(ipp), info_hash_strings);
}
if (!success)
{ {
return; return;
} }
@ -508,28 +627,40 @@ private:
* matter). A listening client on the same network might react by adding us to his * matter). A listening client on the same network might react by adding us to his
* peer pool for torrent t. * peer pool for torrent t.
*/ */
bool sendAnnounce(std::vector<std::string_view> const& info_hash_strings) bool sendAnnounce(tr_address_type ip_protocol, std::vector<std::string_view> const& info_hash_strings)
{ {
auto const announce = makeAnnounceMsg(cookie_, mediator_.port(), info_hash_strings); TR_ASSERT(tr_address::is_valid(ip_protocol));
if (!tr_address::is_valid(ip_protocol))
{
return false;
}
if (mcast_sockets_[ip_protocol] == TR_BAD_SOCKET)
{
return true;
}
auto const announce = makeAnnounceMsg(ip_protocol, cookie_, mediator_.port(), info_hash_strings);
TR_ASSERT(std::size(announce) <= MaxDatagramLength); TR_ASSERT(std::size(announce) <= MaxDatagramLength);
auto const res = sendto( auto const res = sendto(
mcast_socket_, mcast_sockets_[ip_protocol],
std::data(announce), std::data(announce),
std::size(announce), std::size(announce),
0, 0,
reinterpret_cast<sockaddr const*>(&mcast_addr_), ip_protocol == TR_AF_INET ? reinterpret_cast<sockaddr const*>(&mcast_addr_) :
sizeof(mcast_addr_)); reinterpret_cast<sockaddr const*>(&mcast6_addr_),
auto const sent = res == static_cast<int>(std::size(announce)); ip_protocol == TR_AF_INET ? sizeof(mcast_addr_) : sizeof(mcast6_addr_));
return sent; return res == static_cast<int>(std::size(announce));
} }
std::string const cookie_ = makeCookie(); std::string const cookie_ = makeCookie();
Mediator& mediator_; Mediator& mediator_;
tr_socket_t mcast_socket_ = TR_BAD_SOCKET; /**multicast socket */ std::array<tr_socket_t, NUM_TR_AF_INET_TYPES> mcast_sockets_ = { TR_BAD_SOCKET, TR_BAD_SOCKET }; /**multicast sockets */
libtransmission::evhelpers::event_unique_ptr event_; std::array<libtransmission::evhelpers::event_unique_ptr, NUM_TR_AF_INET_TYPES> events_;
static auto constexpr MaxDatagramLength = size_t{ 1400 }; static auto constexpr MaxDatagramLength = size_t{ 1400 };
sockaddr_in mcast_addr_ = {}; /**<initialized from the above constants in init() */ sockaddr_in mcast_addr_ = {}; /**<initialized from the above constants in init() */
sockaddr_in6 mcast6_addr_ = {}; /**<initialized from the above constants in init() */
// BEP14: "To avoid causing multicast storms on large networks a // BEP14: "To avoid causing multicast storms on large networks a
// client should send no more than 1 announce per minute." // client should send no more than 1 announce per minute."

View File

@ -41,9 +41,11 @@ public:
{ {
} }
[[nodiscard]] tr_address bind_address(tr_address_type /* type */) const override [[nodiscard]] tr_address bind_address(tr_address_type type) const override
{ {
return {}; auto ret = tr_address{};
ret.type = type;
return ret;
} }
[[nodiscard]] tr_port port() const override [[nodiscard]] tr_port port() const override

View File

@ -150,7 +150,7 @@ Get a file list for the current torrent(s)
.It Fl g Fl -get Ar all | file-index | files .It Fl g Fl -get Ar all | file-index | files
Mark file(s) for download. Mark file(s) for download.
.Ar all .Ar all
marks all all of the torrent's files for downloading, marks all of the torrent's files for downloading,
.Ar file-index .Ar file-index
adds a single file to the download list, and adds a single file to the download list, and
.Ar files .Ar files