From a0a68281d767d77b039aff8f7624143d29a9db11 Mon Sep 17 00:00:00 2001 From: 999purple999 Date: Fri, 29 May 2026 09:03:54 +0200 Subject: [PATCH 1/6] PortManager: replace uint64_t hash token with exact-tuple PortRangeKey (closes #1805) The previous GeneratePortRangeHash collapsed distinct (protocol, address, port range) tuples into one uint64_t bucket because of two lossy operations: IPv4: hash |= (address >> 2) << 2; // bottom 2 bits dropped IPv6: address = a[0] ^ a[1] ^ a[2] ^ a[3]; // 128 bits XOR-folded to 32 Downstream, `GetOrCreatePortRange` treats a hash hit as "same range" and `Unbind(hash, port)` releases ports against whatever PortRange the hash maps to. In a multi-tenant or multi-interface mediasoup deployment with nearby IPv4 addresses (any /30 block) or XOR-colliding IPv6 addresses, two unrelated bindings get silently merged into one PortRange. The `Unbind` path then releases ports from the wrong tenant's range. This replaces the hash-as-key design with a struct-as-key design: class PortRangeKey { Protocol protocol; sockaddr_storage bindAddr; uint16_t minPort; uint16_t maxPort; bool operator==(const PortRangeKey&) const noexcept; }; struct PortRangeKeyHash { size_t operator()(const PortRangeKey&) const noexcept; }; absl::flat_hash_map mapPortRanges; Map equality is exact-tuple. The hash function (absl::HashOf over protocol + family + raw address bytes + range bounds) is for bucket distribution only; even on hash collision, key equality keeps distinct tuples in distinct entries. API change (per @ibc 2026-05-28: "I am fine with the proposed changes, no need to keep the uint64_t token"): `Bind` now outputs a `PortRangeKey` and `Unbind` takes one. Callers updated: UdpSocket, TcpServer, PipeTransport, PlainTransport, WebRtcServer, WebRtcTransport. Tests: new TestPortManager.cpp asserts that - identical tuples compare equal (correctness), - all 4 IPv4 addresses in 192.168.1.0/30 produce distinct keys (regression for the IPv4 /30 collision), - IPv6 word-swap collisions produce distinct keys (regression for the IPv6 XOR fold), - protocol, range bounds, and family each independently differentiate keys. Co-authored-by: penguinol --- worker/include/RTC/PortManager.hpp | 56 ++++-- worker/include/RTC/TcpServer.hpp | 5 +- worker/include/RTC/UdpSocket.hpp | 5 +- worker/meson.build | 1 + worker/src/RTC/PipeTransport.cpp | 8 +- worker/src/RTC/PlainTransport.cpp | 16 +- worker/src/RTC/PortManager.cpp | 245 ++++++++++++++---------- worker/src/RTC/TcpServer.cpp | 8 +- worker/src/RTC/UdpSocket.cpp | 8 +- worker/src/RTC/WebRtcServer.cpp | 16 +- worker/src/RTC/WebRtcTransport.cpp | 16 +- worker/test/src/RTC/TestPortManager.cpp | 140 ++++++++++++++ 12 files changed, 377 insertions(+), 147 deletions(-) create mode 100644 worker/test/src/RTC/TestPortManager.cpp diff --git a/worker/include/RTC/PortManager.hpp b/worker/include/RTC/PortManager.hpp index f6976970b3..87e457e928 100644 --- a/worker/include/RTC/PortManager.hpp +++ b/worker/include/RTC/PortManager.hpp @@ -5,6 +5,7 @@ #include "RTC/Transport.hpp" #include #include +#include #include #include @@ -12,13 +13,46 @@ namespace RTC { class PortManager { - private: + public: enum class Protocol : uint8_t { UDP = 1, TCP }; + // Opaque-ish key identifying one (protocol, bind address, port range) + // tuple. Issued by Bind*() and consumed by Unbind(). Callers store it + // as an opaque token; equality and hashing are exact-tuple based, so + // distinct tuples never collide regardless of how close their numeric + // representations are. + class PortRangeKey + { + public: + PortRangeKey() = default; + PortRangeKey( + Protocol protocol, const sockaddr_storage& bindAddr, uint16_t minPort, uint16_t maxPort); + + bool operator==(const PortRangeKey& other) const noexcept; + bool operator!=(const PortRangeKey& other) const noexcept + { + return !(*this == other); + } + + private: + friend class PortManager; + friend struct PortRangeKeyHash; + + Protocol protocol{ Protocol::UDP }; + sockaddr_storage bindAddr{}; + uint16_t minPort{ 0u }; + uint16_t maxPort{ 0u }; + }; + + struct PortRangeKeyHash + { + size_t operator()(const PortRangeKey& key) const noexcept; + }; + private: struct PortRange { @@ -42,9 +76,9 @@ namespace RTC uint16_t minPort, uint16_t maxPort, RTC::Transport::SocketFlags& flags, - uint64_t& hash) + PortRangeKey& key) { - return reinterpret_cast(Bind(Protocol::UDP, ip, minPort, maxPort, flags, hash)); + return reinterpret_cast(Bind(Protocol::UDP, ip, minPort, maxPort, flags, key)); } static uv_tcp_t* BindTcp(std::string& ip, uint16_t port, RTC::Transport::SocketFlags& flags) { @@ -55,11 +89,11 @@ namespace RTC uint16_t minPort, uint16_t maxPort, RTC::Transport::SocketFlags& flags, - uint64_t& hash) + PortRangeKey& key) { - return reinterpret_cast(Bind(Protocol::TCP, ip, minPort, maxPort, flags, hash)); + return reinterpret_cast(Bind(Protocol::TCP, ip, minPort, maxPort, flags, key)); } - static void Unbind(uint64_t hash, uint16_t port); + static void Unbind(const PortRangeKey& key, uint16_t port); void Dump(int indentation = 0) const; private: @@ -71,14 +105,14 @@ namespace RTC uint16_t minPort, uint16_t maxPort, RTC::Transport::SocketFlags& flags, - uint64_t& hash); - static uint64_t GeneratePortRangeHash( - Protocol protocol, sockaddr_storage* bindAddr, uint16_t minPort, uint16_t maxPort); - static PortRange& GetOrCreatePortRange(uint64_t hash, uint16_t minPort, uint16_t maxPort); + PortRangeKey& key); + static PortRange& GetOrCreatePortRange( + const PortRangeKey& key, uint16_t minPort, uint16_t maxPort); static uint8_t ConvertSocketFlags(RTC::Transport::SocketFlags& flags, Protocol protocol, int family); private: - static thread_local ankerl::unordered_dense::map mapPortRanges; + static thread_local ankerl::unordered_dense::map + mapPortRanges; }; } // namespace RTC diff --git a/worker/include/RTC/TcpServer.hpp b/worker/include/RTC/TcpServer.hpp index 08c8bdbf5d..b63f777a74 100644 --- a/worker/include/RTC/TcpServer.hpp +++ b/worker/include/RTC/TcpServer.hpp @@ -4,6 +4,7 @@ #include "common.hpp" #include "handles/TcpConnectionHandle.hpp" #include "handles/TcpServerHandle.hpp" +#include "RTC/PortManager.hpp" #include "RTC/TcpConnection.hpp" #include "RTC/Transport.hpp" #include @@ -37,7 +38,7 @@ namespace RTC uint16_t minPort, uint16_t maxPort, RTC::Transport::SocketFlags& flags, - uint64_t& portRangeHash); + RTC::PortManager::PortRangeKey& portRangeKey); ~TcpServer() override; /* Pure virtual methods inherited from ::TcpServerHandle. */ @@ -50,7 +51,7 @@ namespace RTC Listener* listener{ nullptr }; RTC::TcpConnection::Listener* connListener{ nullptr }; bool fixedPort{ false }; - uint64_t portRangeHash{ 0u }; + RTC::PortManager::PortRangeKey portRangeKey{}; }; } // namespace RTC diff --git a/worker/include/RTC/UdpSocket.hpp b/worker/include/RTC/UdpSocket.hpp index d995ee251b..a1e21c56fa 100644 --- a/worker/include/RTC/UdpSocket.hpp +++ b/worker/include/RTC/UdpSocket.hpp @@ -3,6 +3,7 @@ #include "common.hpp" #include "handles/UdpSocketHandle.hpp" +#include "RTC/PortManager.hpp" #include "RTC/Transport.hpp" #include @@ -33,7 +34,7 @@ namespace RTC uint16_t minPort, uint16_t maxPort, RTC::Transport::SocketFlags& flags, - uint64_t& portRangeHash); + RTC::PortManager::PortRangeKey& portRangeKey); ~UdpSocket() override; /* Pure virtual methods inherited from ::UdpSocketHandle. */ @@ -45,7 +46,7 @@ namespace RTC // Passed by argument. Listener* listener{ nullptr }; bool fixedPort{ false }; - uint64_t portRangeHash{ 0u }; + RTC::PortManager::PortRangeKey portRangeKey{}; }; } // namespace RTC diff --git a/worker/meson.build b/worker/meson.build index 798f1ad512..ee1f1ec8ec 100644 --- a/worker/meson.build +++ b/worker/meson.build @@ -451,6 +451,7 @@ test_sources = [ 'test/src/testHelpers.cpp', 'test/src/RTC/TestKeyFrameRequestManager.cpp', 'test/src/RTC/TestNackGenerator.cpp', + 'test/src/RTC/TestPortManager.cpp', 'test/src/RTC/TestRateCalculator.cpp', 'test/src/RTC/TestRtpEncodingParameters.cpp', 'test/src/RTC/TestSeqManager.cpp', diff --git a/worker/src/RTC/PipeTransport.cpp b/worker/src/RTC/PipeTransport.cpp index 31d058ab08..4690d82a5c 100644 --- a/worker/src/RTC/PipeTransport.cpp +++ b/worker/src/RTC/PipeTransport.cpp @@ -72,7 +72,7 @@ namespace RTC { if (this->listenInfo.portRange.min != 0 && this->listenInfo.portRange.max != 0) { - uint64_t portRangeHash{ 0u }; + RTC::PortManager::PortRangeKey portRangeKey{}; this->udpSocket = new RTC::UdpSocket( this, @@ -80,7 +80,7 @@ namespace RTC this->listenInfo.portRange.min, this->listenInfo.portRange.max, this->listenInfo.flags, - portRangeHash); + portRangeKey); } else if (this->listenInfo.port != 0) { @@ -92,7 +92,7 @@ namespace RTC // required. else { - uint64_t portRangeHash{ 0u }; + RTC::PortManager::PortRangeKey portRangeKey{}; this->udpSocket = new RTC::UdpSocket( this, @@ -100,7 +100,7 @@ namespace RTC Settings::configuration.rtcMinPort, Settings::configuration.rtcMaxPort, this->listenInfo.flags, - portRangeHash); + portRangeKey); } if (this->listenInfo.sendBufferSize != 0) diff --git a/worker/src/RTC/PlainTransport.cpp b/worker/src/RTC/PlainTransport.cpp index d35927eb7b..59d2b154fe 100644 --- a/worker/src/RTC/PlainTransport.cpp +++ b/worker/src/RTC/PlainTransport.cpp @@ -151,7 +151,7 @@ namespace RTC { if (this->listenInfo.portRange.min != 0 && this->listenInfo.portRange.max != 0) { - uint64_t portRangeHash{ 0u }; + RTC::PortManager::PortRangeKey portRangeKey{}; this->udpSocket = new RTC::UdpSocket( this, @@ -159,7 +159,7 @@ namespace RTC this->listenInfo.portRange.min, this->listenInfo.portRange.max, this->listenInfo.flags, - portRangeHash); + portRangeKey); } else if (this->listenInfo.port != 0) { @@ -171,7 +171,7 @@ namespace RTC // required. else { - uint64_t portRangeHash{ 0u }; + RTC::PortManager::PortRangeKey portRangeKey{}; this->udpSocket = new RTC::UdpSocket( this, @@ -179,7 +179,7 @@ namespace RTC Settings::configuration.rtcMinPort, Settings::configuration.rtcMaxPort, this->listenInfo.flags, - portRangeHash); + portRangeKey); } if (this->listenInfo.sendBufferSize != 0) @@ -198,7 +198,7 @@ namespace RTC { if (this->rtcpListenInfo.portRange.min != 0 && this->rtcpListenInfo.portRange.max != 0) { - uint64_t portRangeHash{ 0u }; + RTC::PortManager::PortRangeKey portRangeKey{}; this->rtcpUdpSocket = new RTC::UdpSocket( this, @@ -206,7 +206,7 @@ namespace RTC this->rtcpListenInfo.portRange.min, this->rtcpListenInfo.portRange.max, this->rtcpListenInfo.flags, - portRangeHash); + portRangeKey); } else if (this->rtcpListenInfo.port != 0) { @@ -218,7 +218,7 @@ namespace RTC // required. else { - uint64_t portRangeHash{ 0u }; + RTC::PortManager::PortRangeKey portRangeKey{}; this->rtcpUdpSocket = new RTC::UdpSocket( this, @@ -226,7 +226,7 @@ namespace RTC Settings::configuration.rtcMinPort, Settings::configuration.rtcMaxPort, this->rtcpListenInfo.flags, - portRangeHash); + portRangeKey); } if (this->rtcpListenInfo.sendBufferSize != 0) diff --git a/worker/src/RTC/PortManager.cpp b/worker/src/RTC/PortManager.cpp index f960ea4ceb..42a153751d 100644 --- a/worker/src/RTC/PortManager.cpp +++ b/worker/src/RTC/PortManager.cpp @@ -6,7 +6,8 @@ #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "Utils.hpp" -#include // std:make_tuple() +#include // std::memcmp(), std::memset() +#include // std::make_tuple() /* Static methods for UV callbacks. */ @@ -31,7 +32,134 @@ namespace RTC { /* Class variables. */ - thread_local ankerl::unordered_dense::map PortManager::mapPortRanges; + thread_local ankerl::unordered_dense:: + map + PortManager::mapPortRanges; + + /* PortRangeKey methods. */ + + PortManager::PortRangeKey::PortRangeKey( + Protocol protocol, const sockaddr_storage& bindAddr, uint16_t minPort, uint16_t maxPort) + : protocol(protocol), bindAddr(bindAddr), minPort(minPort), maxPort(maxPort) + { + // sockaddr_storage is padded; the unused tail bytes are caller-controlled. + // operator== inspects only the meaningful address bytes (sin_addr / + // sin6_addr) so padding does not affect equality, and the hash function + // hashes the same exact fields, so two structurally-equal keys always + // produce the same hash regardless of how the caller zero-initialized. + } + + bool PortManager::PortRangeKey::operator==(const PortRangeKey& other) const noexcept + { + if (this->protocol != other.protocol) + { + return false; + } + if (this->minPort != other.minPort) + { + return false; + } + if (this->maxPort != other.maxPort) + { + return false; + } + if (this->bindAddr.ss_family != other.bindAddr.ss_family) + { + return false; + } + + switch (this->bindAddr.ss_family) + { + case AF_INET: + { + const auto* a = reinterpret_cast(&this->bindAddr); + const auto* b = reinterpret_cast(&other.bindAddr); + + return a->sin_addr.s_addr == b->sin_addr.s_addr; + } + + case AF_INET6: + { + const auto* a = reinterpret_cast(&this->bindAddr); + const auto* b = reinterpret_cast(&other.bindAddr); + + return std::memcmp(&a->sin6_addr, &b->sin6_addr, sizeof(in6_addr)) == 0; + } + + default: + { + // Unknown family; treat as not equal to avoid accidental merge. + return false; + } + } + } + + // Hash function. Uses ankerl::unordered_dense per-field hashes combined with + // the standard boost-style hash_combine seed mixer. We deliberately do NOT + // hash the raw sockaddr_storage bytes (padding is caller-controlled and + // would cause structurally-equal keys to hash differently); instead we hash + // the same fields that operator== inspects. + size_t PortManager::PortRangeKeyHash::operator()(const PortRangeKey& key) const noexcept + { + const auto protocolBits = static_cast(key.protocol); + const auto familyBits = static_cast(key.bindAddr.ss_family); + + auto hashCombine = [](size_t& seed, size_t value) + { + seed ^= value + 0x9e3779b9 + (seed << 6) + (seed >> 2); + }; + + size_t seed = 0; + + switch (key.bindAddr.ss_family) + { + case AF_INET: + { + const auto* in = reinterpret_cast(&key.bindAddr); + + hashCombine(seed, ankerl::unordered_dense::hash{}(protocolBits)); + hashCombine(seed, ankerl::unordered_dense::hash{}(familyBits)); + hashCombine(seed, ankerl::unordered_dense::hash{}(in->sin_addr.s_addr)); + hashCombine(seed, ankerl::unordered_dense::hash{}(key.minPort)); + hashCombine(seed, ankerl::unordered_dense::hash{}(key.maxPort)); + + break; + } + + case AF_INET6: + { + const auto* in6 = reinterpret_cast(&key.bindAddr); + const auto* addr = in6->sin6_addr.s6_addr; + + uint64_t hi; + uint64_t lo; + + std::memcpy(&hi, addr, sizeof(uint64_t)); + std::memcpy(&lo, addr + sizeof(uint64_t), sizeof(uint64_t)); + + hashCombine(seed, ankerl::unordered_dense::hash{}(protocolBits)); + hashCombine(seed, ankerl::unordered_dense::hash{}(familyBits)); + hashCombine(seed, ankerl::unordered_dense::hash{}(hi)); + hashCombine(seed, ankerl::unordered_dense::hash{}(lo)); + hashCombine(seed, ankerl::unordered_dense::hash{}(key.minPort)); + hashCombine(seed, ankerl::unordered_dense::hash{}(key.maxPort)); + + break; + } + + default: + { + hashCombine(seed, ankerl::unordered_dense::hash{}(protocolBits)); + hashCombine(seed, ankerl::unordered_dense::hash{}(familyBits)); + hashCombine(seed, ankerl::unordered_dense::hash{}(key.minPort)); + hashCombine(seed, ankerl::unordered_dense::hash{}(key.maxPort)); + + break; + } + } + + return seed; + } /* Class methods. */ @@ -250,7 +378,7 @@ namespace RTC uint16_t minPort, uint16_t maxPort, RTC::Transport::SocketFlags& flags, - uint64_t& hash) + PortRangeKey& key) { MS_TRACE(); @@ -317,9 +445,9 @@ namespace RTC } } - hash = GeneratePortRangeHash(protocol, std::addressof(bindAddr), minPort, maxPort); + key = PortRangeKey(protocol, bindAddr, minPort, maxPort); - auto& portRange = PortManager::GetOrCreatePortRange(hash, minPort, maxPort); + auto& portRange = PortManager::GetOrCreatePortRange(key, minPort, maxPort); const size_t numPorts = portRange.ports.size(); const size_t numAttempts = numPorts; size_t attempt{ 0u }; @@ -595,16 +723,19 @@ namespace RTC return uvHandle; } - void PortManager::Unbind(uint64_t hash, uint16_t port) + void PortManager::Unbind(const PortRangeKey& key, uint16_t port) { MS_TRACE(); - auto it = PortManager::mapPortRanges.find(hash); + auto it = PortManager::mapPortRanges.find(key); // This should not happen. if (it == PortManager::mapPortRanges.end()) { - MS_ERROR("hash %" PRIu64 " doesn't exist in the map", hash); + MS_ERROR( + "port range key [minPort:%" PRIu16 ", maxPort:%" PRIu16 "] doesn't exist in the map", + key.minPort, + key.maxPort); return; } @@ -635,11 +766,14 @@ namespace RTC for (auto& kv : PortManager::mapPortRanges) { - auto hash = kv.first; - auto portRange = kv.second; + const auto& key = kv.first; + const auto& portRange = kv.second; + const char* protocolStr = + (key.protocol == Protocol::UDP) ? "udp" : "tcp"; MS_DUMP_CLEAN(indentation + 1, ""); - MS_DUMP_CLEAN(indentation + 1, " hash: %" PRIu64, hash); + MS_DUMP_CLEAN(indentation + 1, " protocol: %s", protocolStr); + MS_DUMP_CLEAN(indentation + 1, " family: %d", key.bindAddr.ss_family); MS_DUMP_CLEAN(indentation + 1, " minPort: %" PRIu16, portRange.minPort); MS_DUMP_CLEAN(indentation + 1, " maxPort: %zu", portRange.minPort + portRange.ports.size() - 1); MS_DUMP_CLEAN(indentation + 1, " numUsedPorts: %" PRIu16, portRange.numUsedPorts); @@ -649,95 +783,14 @@ namespace RTC MS_DUMP_CLEAN(indentation, ""); } - /* - * Hash for IPv4. - * - * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | MIN PORT | MAX PORT | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | IP | IP >> 2 |F|P| - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * - * Hash for IPv6. - * - * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | MIN PORT | MAX PORT | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * |IP[0] ^ IP[1] ^ IP[2] ^ IP[3] | same >> 2 |F|P| - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - */ - uint64_t PortManager::GeneratePortRangeHash( - Protocol protocol, sockaddr_storage* bindAddr, uint16_t minPort, uint16_t maxPort) - { - MS_TRACE(); - - uint64_t hash{ 0u }; - - switch (bindAddr->ss_family) - { - case AF_INET: - { - auto* bindAddrIn = reinterpret_cast(bindAddr); - - // We want it in network order. - const uint64_t address = bindAddrIn->sin_addr.s_addr; - - hash |= static_cast(minPort) << 48; - hash |= static_cast(maxPort) << 32; - hash |= (address >> 2) << 2; - hash |= 0x0000; // AF_INET. - - break; - } - - case AF_INET6: - { - auto* bindAddrIn6 = reinterpret_cast(bindAddr); - auto* a = reinterpret_cast(std::addressof(bindAddrIn6->sin6_addr)); - - const auto address = a[0] ^ a[1] ^ a[2] ^ a[3]; - - hash |= static_cast(minPort) << 48; - hash |= static_cast(maxPort) << 32; - hash |= static_cast(address) << 16; - hash |= (static_cast(address) >> 2) << 2; - hash |= 0x0002; // AF_INET6. - - break; - } - - // This cannot happen. - default: - { - MS_THROW_ERROR("unknown IP family"); - } - } - - // Override least significant bit with protocol information: - // - If UDP, start with 0. - // - If TCP, start with 1. - if (protocol == Protocol::UDP) - { - hash |= 0x0000; - } - else - { - hash |= 0x0001; - } - - return hash; - } - PortManager::PortRange& PortManager::GetOrCreatePortRange( - uint64_t hash, uint16_t minPort, uint16_t maxPort) + const PortRangeKey& key, uint16_t minPort, uint16_t maxPort) { MS_TRACE(); - auto it = PortManager::mapPortRanges.find(hash); + auto it = PortManager::mapPortRanges.find(key); - // If the hash is already handled, return its port range. + // If the key is already handled, return its port range. if (it != PortManager::mapPortRanges.end()) { auto& portRange = it->second; @@ -750,7 +803,7 @@ namespace RTC // Emplace a new vector filled with numPorts false values, meaning that // all ports are available. auto pair = PortManager::mapPortRanges.emplace( - std::piecewise_construct, std::make_tuple(hash), std::make_tuple(numPorts, minPort)); + std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(numPorts, minPort)); // pair.first is an iterator to the inserted value. auto& portRange = pair.first->second; diff --git a/worker/src/RTC/TcpServer.cpp b/worker/src/RTC/TcpServer.cpp index f123faa77b..eb88151547 100644 --- a/worker/src/RTC/TcpServer.cpp +++ b/worker/src/RTC/TcpServer.cpp @@ -36,16 +36,16 @@ namespace RTC uint16_t minPort, uint16_t maxPort, RTC::Transport::SocketFlags& flags, - uint64_t& portRangeHash) + RTC::PortManager::PortRangeKey& portRangeKey) : // This may throw. ::TcpServerHandle::TcpServerHandle( - RTC::PortManager::BindTcp(ip, minPort, maxPort, flags, portRangeHash)), + RTC::PortManager::BindTcp(ip, minPort, maxPort, flags, portRangeKey)), listener(listener), connListener(connListener) { MS_TRACE(); - this->portRangeHash = portRangeHash; + this->portRangeKey = portRangeKey; } TcpServer::~TcpServer() @@ -54,7 +54,7 @@ namespace RTC if (!this->fixedPort) { - RTC::PortManager::Unbind(this->portRangeHash, this->localPort); + RTC::PortManager::Unbind(this->portRangeKey, this->localPort); } } diff --git a/worker/src/RTC/UdpSocket.cpp b/worker/src/RTC/UdpSocket.cpp index fa4b14f2d7..748cc38805 100644 --- a/worker/src/RTC/UdpSocket.cpp +++ b/worker/src/RTC/UdpSocket.cpp @@ -26,15 +26,15 @@ namespace RTC uint16_t minPort, uint16_t maxPort, RTC::Transport::SocketFlags& flags, - uint64_t& portRangeHash) + RTC::PortManager::PortRangeKey& portRangeKey) : // This may throw. ::UdpSocketHandle::UdpSocketHandle( - RTC::PortManager::BindUdp(ip, minPort, maxPort, flags, portRangeHash)), + RTC::PortManager::BindUdp(ip, minPort, maxPort, flags, portRangeKey)), listener(listener) { MS_TRACE(); - this->portRangeHash = portRangeHash; + this->portRangeKey = portRangeKey; } UdpSocket::~UdpSocket() @@ -43,7 +43,7 @@ namespace RTC if (!this->fixedPort) { - RTC::PortManager::Unbind(this->portRangeHash, this->localPort); + RTC::PortManager::Unbind(this->portRangeKey, this->localPort); } } diff --git a/worker/src/RTC/WebRtcServer.cpp b/worker/src/RTC/WebRtcServer.cpp index 38cbf3ebdb..27f192acc5 100644 --- a/worker/src/RTC/WebRtcServer.cpp +++ b/worker/src/RTC/WebRtcServer.cpp @@ -96,7 +96,7 @@ namespace RTC if (listenInfo->portRange()->min() != 0 && listenInfo->portRange()->max() != 0) { - uint64_t portRangeHash{ 0u }; + RTC::PortManager::PortRangeKey portRangeKey{}; udpSocket = new RTC::UdpSocket( this, @@ -104,7 +104,7 @@ namespace RTC listenInfo->portRange()->min(), listenInfo->portRange()->max(), flags, - portRangeHash); + portRangeKey); } else if (listenInfo->port() != 0) { @@ -115,7 +115,7 @@ namespace RTC // required. else { - uint64_t portRangeHash{ 0u }; + RTC::PortManager::PortRangeKey portRangeKey{}; udpSocket = new RTC::UdpSocket( this, @@ -123,7 +123,7 @@ namespace RTC Settings::configuration.rtcMinPort, Settings::configuration.rtcMaxPort, flags, - portRangeHash); + portRangeKey); } this->udpSocketOrTcpServers.emplace_back( @@ -152,7 +152,7 @@ namespace RTC if (listenInfo->portRange()->min() != 0 && listenInfo->portRange()->max() != 0) { - uint64_t portRangeHash{ 0u }; + RTC::PortManager::PortRangeKey portRangeKey{}; tcpServer = new RTC::TcpServer( this, @@ -161,7 +161,7 @@ namespace RTC listenInfo->portRange()->min(), listenInfo->portRange()->max(), flags, - portRangeHash); + portRangeKey); } else if (listenInfo->port() != 0) { @@ -172,7 +172,7 @@ namespace RTC // required. else { - uint64_t portRangeHash{ 0u }; + RTC::PortManager::PortRangeKey portRangeKey{}; tcpServer = new RTC::TcpServer( this, @@ -181,7 +181,7 @@ namespace RTC Settings::configuration.rtcMinPort, Settings::configuration.rtcMaxPort, flags, - portRangeHash); + portRangeKey); } this->udpSocketOrTcpServers.emplace_back( diff --git a/worker/src/RTC/WebRtcTransport.cpp b/worker/src/RTC/WebRtcTransport.cpp index 0384415578..5d94b81715 100644 --- a/worker/src/RTC/WebRtcTransport.cpp +++ b/worker/src/RTC/WebRtcTransport.cpp @@ -81,7 +81,7 @@ namespace RTC if (listenInfo->portRange()->min() != 0 && listenInfo->portRange()->max() != 0) { - uint64_t portRangeHash{ 0u }; + RTC::PortManager::PortRangeKey portRangeKey{}; udpSocket = new RTC::UdpSocket( this, @@ -89,7 +89,7 @@ namespace RTC listenInfo->portRange()->min(), listenInfo->portRange()->max(), flags, - portRangeHash); + portRangeKey); } else if (listenInfo->port() != 0) { @@ -100,7 +100,7 @@ namespace RTC // required. else { - uint64_t portRangeHash{ 0u }; + RTC::PortManager::PortRangeKey portRangeKey{}; udpSocket = new RTC::UdpSocket( this, @@ -108,7 +108,7 @@ namespace RTC Settings::configuration.rtcMinPort, Settings::configuration.rtcMaxPort, flags, - portRangeHash); + portRangeKey); } this->udpSockets[udpSocket] = announcedAddress; @@ -151,7 +151,7 @@ namespace RTC if (listenInfo->portRange()->min() != 0 && listenInfo->portRange()->max() != 0) { - uint64_t portRangeHash{ 0u }; + RTC::PortManager::PortRangeKey portRangeKey{}; tcpServer = new RTC::TcpServer( this, @@ -160,7 +160,7 @@ namespace RTC listenInfo->portRange()->min(), listenInfo->portRange()->max(), flags, - portRangeHash); + portRangeKey); } else if (listenInfo->port() != 0) { @@ -171,7 +171,7 @@ namespace RTC // required. else { - uint64_t portRangeHash{ 0u }; + RTC::PortManager::PortRangeKey portRangeKey{}; tcpServer = new RTC::TcpServer( this, @@ -180,7 +180,7 @@ namespace RTC Settings::configuration.rtcMinPort, Settings::configuration.rtcMaxPort, flags, - portRangeHash); + portRangeKey); } this->tcpServers[tcpServer] = announcedAddress; diff --git a/worker/test/src/RTC/TestPortManager.cpp b/worker/test/src/RTC/TestPortManager.cpp new file mode 100644 index 0000000000..6435482735 --- /dev/null +++ b/worker/test/src/RTC/TestPortManager.cpp @@ -0,0 +1,140 @@ +#include "common.hpp" +#include "RTC/PortManager.hpp" +#include +#include +#include +#include +#include + +namespace +{ + // Helper: build an IPv4 sockaddr_storage from a dotted-quad string + port=0. + sockaddr_storage MakeV4(const char* dottedQuad) + { + sockaddr_storage ss{}; + auto* in = reinterpret_cast(&ss); + in->sin_family = AF_INET; + in->sin_port = 0; + REQUIRE(inet_pton(AF_INET, dottedQuad, &in->sin_addr) == 1); + + return ss; + } + + // Helper: build an IPv6 sockaddr_storage from a textual address + port=0. + sockaddr_storage MakeV6(const char* literal) + { + sockaddr_storage ss{}; + auto* in6 = reinterpret_cast(&ss); + in6->sin6_family = AF_INET6; + in6->sin6_port = 0; + REQUIRE(inet_pton(AF_INET6, literal, &in6->sin6_addr) == 1); + + return ss; + } +} // namespace + +SCENARIO("PortManager::PortRangeKey - exact-tuple semantics (issue #1805)", "[rtc][port-manager]") +{ + using RTC::PortManager; + + // Pre-fix issue #1805: the legacy GeneratePortRangeHash mangled the IPv4 + // address with `(address >> 2) << 2`, so any two IPv4 addresses in the + // same /30 block produced the same uint64_t hash. The downstream + // `mapPortRanges.find(hash)` then treated them as the same PortRange and + // merged unrelated bindings. This scenario locks down the post-fix + // behavior: distinct tuples produce distinct keys, equal tuples produce + // equal keys. + + SECTION("identical tuples compare equal and hash equal") + { + const auto addr = MakeV4("192.168.1.10"); + + const PortManager::PortRangeKey a( + PortManager::Protocol::UDP, addr, 40000u, 40099u); + const PortManager::PortRangeKey b( + PortManager::Protocol::UDP, addr, 40000u, 40099u); + + REQUIRE(a == b); + REQUIRE(PortManager::PortRangeKeyHash{}(a) == PortManager::PortRangeKeyHash{}(b)); + } + + SECTION("IPv4 addresses in the same /30 are NOT collapsed (was #1805)") + { + // 192.168.1.0 / .1 / .2 / .3 all live in 192.168.1.0/30. The old + // GeneratePortRangeHash dropped the bottom two bits of the address, + // merging all four into one bucket. + const auto a0 = MakeV4("192.168.1.0"); + const auto a1 = MakeV4("192.168.1.1"); + const auto a2 = MakeV4("192.168.1.2"); + const auto a3 = MakeV4("192.168.1.3"); + + const PortManager::PortRangeKey k0(PortManager::Protocol::UDP, a0, 40000u, 40099u); + const PortManager::PortRangeKey k1(PortManager::Protocol::UDP, a1, 40000u, 40099u); + const PortManager::PortRangeKey k2(PortManager::Protocol::UDP, a2, 40000u, 40099u); + const PortManager::PortRangeKey k3(PortManager::Protocol::UDP, a3, 40000u, 40099u); + + REQUIRE(k0 != k1); + REQUIRE(k0 != k2); + REQUIRE(k0 != k3); + REQUIRE(k1 != k2); + REQUIRE(k1 != k3); + REQUIRE(k2 != k3); + } + + SECTION("IPv6 addresses that XOR-fold to the same value are NOT collapsed") + { + // The old IPv6 hash folded `a[0] ^ a[1] ^ a[2] ^ a[3]` (4 x uint32_t) + // into 32 bits. Any two IPv6 addresses where the four 32-bit words + // XOR to the same value collided. Easiest collision constructor: swap + // two words. ::1 = 0000:0000:0000:0000:0000:0000:0000:0001 XOR-folds + // the same as ::1:0:0 (just word reorder). + const auto a = MakeV6("::1"); + const auto b = MakeV6("1::1:0:0:0"); + + const PortManager::PortRangeKey ka(PortManager::Protocol::UDP, a, 40000u, 40099u); + const PortManager::PortRangeKey kb(PortManager::Protocol::UDP, b, 40000u, 40099u); + + REQUIRE(ka != kb); + } + + SECTION("Protocol differentiates the key (UDP/TCP on same address+range)") + { + const auto addr = MakeV4("10.0.0.1"); + + const PortManager::PortRangeKey udp( + PortManager::Protocol::UDP, addr, 40000u, 40099u); + const PortManager::PortRangeKey tcp( + PortManager::Protocol::TCP, addr, 40000u, 40099u); + + REQUIRE(udp != tcp); + } + + SECTION("Port-range bounds differentiate the key") + { + const auto addr = MakeV4("10.0.0.1"); + + const PortManager::PortRangeKey a( + PortManager::Protocol::UDP, addr, 40000u, 40099u); + const PortManager::PortRangeKey b( + PortManager::Protocol::UDP, addr, 40000u, 40100u); + const PortManager::PortRangeKey c( + PortManager::Protocol::UDP, addr, 40001u, 40099u); + + REQUIRE(a != b); + REQUIRE(a != c); + REQUIRE(b != c); + } + + SECTION("Family differentiates the key") + { + const auto v4 = MakeV4("0.0.0.0"); + const auto v6 = MakeV6("::"); + + const PortManager::PortRangeKey k4( + PortManager::Protocol::UDP, v4, 40000u, 40099u); + const PortManager::PortRangeKey k6( + PortManager::Protocol::UDP, v6, 40000u, 40099u); + + REQUIRE(k4 != k6); + } +} From 5a76abd718b7e23e094a741a1d4d2de1dfbcaaa8 Mon Sep 17 00:00:00 2001 From: 999purple999 Date: Fri, 29 May 2026 11:15:16 +0200 Subject: [PATCH 2/6] test: update TestTransportTuple.cpp helper to use PortRangeKey (fix CI) makeUdpSocket lambda was still constructing UdpSocket range ctor with uint64_t portRangeHash as the 6th argument. After replacing the token type with PortRangeKey in b6ff2d8, the helper needed the new type too. --- worker/test/src/RTC/TestTransportTuple.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worker/test/src/RTC/TestTransportTuple.cpp b/worker/test/src/RTC/TestTransportTuple.cpp index ea67b4262c..2bbcee7eaa 100644 --- a/worker/test/src/RTC/TestTransportTuple.cpp +++ b/worker/test/src/RTC/TestTransportTuple.cpp @@ -24,9 +24,9 @@ SCENARIO("TransportTuple", "[transport-tuple]") { UdpSocketListener listener; auto flags = RTC::Transport::SocketFlags{ .ipv6Only = false, .udpReusePort = false }; - uint64_t portRangeHash{ 0u }; + RTC::PortManager::PortRangeKey portRangeKey{}; auto* udpSocket = new RTC::UdpSocket( - std::addressof(listener), const_cast(ip), minPort, maxPort, flags, portRangeHash); + std::addressof(listener), const_cast(ip), minPort, maxPort, flags, portRangeKey); return std::unique_ptr(udpSocket); }; From bdf22cce53f3d969dd863299840e66f132dd54a8 Mon Sep 17 00:00:00 2001 From: 999purple999 Date: Fri, 29 May 2026 12:06:49 +0200 Subject: [PATCH 3/6] test: rename MakeV4/MakeV6 helpers to camelCase (clang-tidy naming) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit clang-tidy on worker-clang-tidy job flagged readability-identifier-naming violations on the two helper functions in TestPortManager.cpp: MakeV4 → makeV4 MakeV6 → makeV6 mediasoup convention is lowerCamelCase for free functions. Mechanical fix of the only two warnings clang-tidy raised on this PR. --- worker/test/src/RTC/TestPortManager.cpp | 26 ++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/worker/test/src/RTC/TestPortManager.cpp b/worker/test/src/RTC/TestPortManager.cpp index 6435482735..0de7c1622c 100644 --- a/worker/test/src/RTC/TestPortManager.cpp +++ b/worker/test/src/RTC/TestPortManager.cpp @@ -9,7 +9,7 @@ namespace { // Helper: build an IPv4 sockaddr_storage from a dotted-quad string + port=0. - sockaddr_storage MakeV4(const char* dottedQuad) + sockaddr_storage makeV4(const char* dottedQuad) { sockaddr_storage ss{}; auto* in = reinterpret_cast(&ss); @@ -21,7 +21,7 @@ namespace } // Helper: build an IPv6 sockaddr_storage from a textual address + port=0. - sockaddr_storage MakeV6(const char* literal) + sockaddr_storage makeV6(const char* literal) { sockaddr_storage ss{}; auto* in6 = reinterpret_cast(&ss); @@ -47,7 +47,7 @@ SCENARIO("PortManager::PortRangeKey - exact-tuple semantics (issue #1805)", "[rt SECTION("identical tuples compare equal and hash equal") { - const auto addr = MakeV4("192.168.1.10"); + const auto addr = makeV4("192.168.1.10"); const PortManager::PortRangeKey a( PortManager::Protocol::UDP, addr, 40000u, 40099u); @@ -63,10 +63,10 @@ SCENARIO("PortManager::PortRangeKey - exact-tuple semantics (issue #1805)", "[rt // 192.168.1.0 / .1 / .2 / .3 all live in 192.168.1.0/30. The old // GeneratePortRangeHash dropped the bottom two bits of the address, // merging all four into one bucket. - const auto a0 = MakeV4("192.168.1.0"); - const auto a1 = MakeV4("192.168.1.1"); - const auto a2 = MakeV4("192.168.1.2"); - const auto a3 = MakeV4("192.168.1.3"); + const auto a0 = makeV4("192.168.1.0"); + const auto a1 = makeV4("192.168.1.1"); + const auto a2 = makeV4("192.168.1.2"); + const auto a3 = makeV4("192.168.1.3"); const PortManager::PortRangeKey k0(PortManager::Protocol::UDP, a0, 40000u, 40099u); const PortManager::PortRangeKey k1(PortManager::Protocol::UDP, a1, 40000u, 40099u); @@ -88,8 +88,8 @@ SCENARIO("PortManager::PortRangeKey - exact-tuple semantics (issue #1805)", "[rt // XOR to the same value collided. Easiest collision constructor: swap // two words. ::1 = 0000:0000:0000:0000:0000:0000:0000:0001 XOR-folds // the same as ::1:0:0 (just word reorder). - const auto a = MakeV6("::1"); - const auto b = MakeV6("1::1:0:0:0"); + const auto a = makeV6("::1"); + const auto b = makeV6("1::1:0:0:0"); const PortManager::PortRangeKey ka(PortManager::Protocol::UDP, a, 40000u, 40099u); const PortManager::PortRangeKey kb(PortManager::Protocol::UDP, b, 40000u, 40099u); @@ -99,7 +99,7 @@ SCENARIO("PortManager::PortRangeKey - exact-tuple semantics (issue #1805)", "[rt SECTION("Protocol differentiates the key (UDP/TCP on same address+range)") { - const auto addr = MakeV4("10.0.0.1"); + const auto addr = makeV4("10.0.0.1"); const PortManager::PortRangeKey udp( PortManager::Protocol::UDP, addr, 40000u, 40099u); @@ -111,7 +111,7 @@ SCENARIO("PortManager::PortRangeKey - exact-tuple semantics (issue #1805)", "[rt SECTION("Port-range bounds differentiate the key") { - const auto addr = MakeV4("10.0.0.1"); + const auto addr = makeV4("10.0.0.1"); const PortManager::PortRangeKey a( PortManager::Protocol::UDP, addr, 40000u, 40099u); @@ -127,8 +127,8 @@ SCENARIO("PortManager::PortRangeKey - exact-tuple semantics (issue #1805)", "[rt SECTION("Family differentiates the key") { - const auto v4 = MakeV4("0.0.0.0"); - const auto v6 = MakeV6("::"); + const auto v4 = makeV4("0.0.0.0"); + const auto v6 = makeV6("::"); const PortManager::PortRangeKey k4( PortManager::Protocol::UDP, v4, 40000u, 40099u); From 23fd75b221781dff292b715891affd1c1db6610d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C3=B1aki=20Baz=20Castillo?= Date: Mon, 1 Jun 2026 23:05:32 +0200 Subject: [PATCH 4/6] cosmetic changes: - Do not include headers already present in `common.hpp`. - Add missing `#include "RTC/PortManager.hpp" in some files. - Reorder classes/structs/methods. - Use `std::addressof(x)` instead of `&c`. - Do not use `using`. Be always explicit and include all namespaces. --- worker/include/RTC/PortManager.hpp | 72 ++++- worker/src/RTC/PipeTransport.cpp | 5 +- worker/src/RTC/PlainTransport.cpp | 9 +- worker/src/RTC/PortManager.cpp | 303 +++++++++++---------- worker/src/RTC/WebRtcTransport.cpp | 5 +- worker/test/src/RTC/TestNackGenerator.cpp | 2 +- worker/test/src/RTC/TestPortManager.cpp | 116 ++++---- worker/test/src/RTC/TestTransportTuple.cpp | 1 + 8 files changed, 278 insertions(+), 235 deletions(-) diff --git a/worker/include/RTC/PortManager.hpp b/worker/include/RTC/PortManager.hpp index 87e457e928..893b7a123d 100644 --- a/worker/include/RTC/PortManager.hpp +++ b/worker/include/RTC/PortManager.hpp @@ -3,9 +3,10 @@ #include "common.hpp" #include "RTC/Transport.hpp" +#include "Utils.hpp" #include #include -#include +#include #include #include @@ -20,13 +21,19 @@ namespace RTC TCP }; - // Opaque-ish key identifying one (protocol, bind address, port range) - // tuple. Issued by Bind*() and consumed by Unbind(). Callers store it - // as an opaque token; equality and hashing are exact-tuple based, so - // distinct tuples never collide regardless of how close their numeric - // representations are. + /** + * Opaque-ish key identifying one (protocol, bind address, port range) + * tuple. Issued by Bind*() and consumed by Unbind(). Callers store it as + * an opaque token; equality and hashing are exact-tuple based, so distinct + * tuples never collide regardless of how close their numeric + * representations are. + */ class PortRangeKey { + private: + friend class PortManager; + friend struct PortRangeKeyHash; + public: PortRangeKey() = default; PortRangeKey( @@ -38,10 +45,25 @@ namespace RTC return !(*this == other); } - private: - friend class PortManager; - friend struct PortRangeKeyHash; + public: + Protocol GetProtocol() const + { + return this->protocol; + } + const sockaddr_storage& GetSockaddrStorage() const + { + return this->bindAddr; + } + uint16_t GetMinPort() const + { + return this->minPort; + } + uint16_t GetMaxPort() const + { + return this->maxPort; + } + private: Protocol protocol{ Protocol::UDP }; sockaddr_storage bindAddr{}; uint16_t minPort{ 0u }; @@ -50,6 +72,14 @@ namespace RTC struct PortRangeKeyHash { + /** + * Hash function. Uses ankerl::unordered_dense per-field hashes combined + * with the standard boost-style `hash_combine` seed mixer. We deliberately + * do NOT hash the raw `sockaddr_storage` bytes (padding is + * caller-controlled and would cause structurally-equal keys to hash + * differently); instead we hash the same fields that `operator==` + * inspects. + */ size_t operator()(const PortRangeKey& key) const noexcept; }; @@ -106,14 +136,30 @@ namespace RTC uint16_t maxPort, RTC::Transport::SocketFlags& flags, PortRangeKey& key); - static PortRange& GetOrCreatePortRange( - const PortRangeKey& key, uint16_t minPort, uint16_t maxPort); + static PortRange& GetOrCreatePortRange(const PortRangeKey& key, uint16_t minPort, uint16_t maxPort); static uint8_t ConvertSocketFlags(RTC::Transport::SocketFlags& flags, Protocol protocol, int family); private: - static thread_local ankerl::unordered_dense::map - mapPortRanges; + static thread_local ankerl::unordered_dense::map mapPortRanges; }; + + /** + * For Catch2 to print it nicely. + */ + inline std::ostream& operator<<(std::ostream& os, const PortManager::PortRangeKey& k) + { + const std::string protocolStr = (k.GetProtocol() == PortManager::Protocol::UDP) ? "udp" : "tcp"; + + int family; + uint16_t port; + std::string ip; + auto* storage = const_cast(std::addressof(k.GetSockaddrStorage())); + + Utils::IP::GetAddressInfo(reinterpret_cast(storage), family, ip, port); + + return os << "{protocol:" << protocolStr << ", family:" << family << ", ip:" << ip + << ", minPort:" << k.GetMinPort() << ", maxPort:" << k.GetMaxPort() << "}"; + } } // namespace RTC #endif diff --git a/worker/src/RTC/PipeTransport.cpp b/worker/src/RTC/PipeTransport.cpp index 4690d82a5c..b4723e9829 100644 --- a/worker/src/RTC/PipeTransport.cpp +++ b/worker/src/RTC/PipeTransport.cpp @@ -4,6 +4,7 @@ #include "RTC/PipeTransport.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" +#include "RTC/PortManager.hpp" #include "RTC/SCTP/packet/Packet.hpp" #include "Settings.hpp" #include "Utils.hpp" @@ -72,7 +73,7 @@ namespace RTC { if (this->listenInfo.portRange.min != 0 && this->listenInfo.portRange.max != 0) { - RTC::PortManager::PortRangeKey portRangeKey{}; + RTC::PortManager::PortRangeKey portRangeKey; this->udpSocket = new RTC::UdpSocket( this, @@ -92,7 +93,7 @@ namespace RTC // required. else { - RTC::PortManager::PortRangeKey portRangeKey{}; + RTC::PortManager::PortRangeKey portRangeKey; this->udpSocket = new RTC::UdpSocket( this, diff --git a/worker/src/RTC/PlainTransport.cpp b/worker/src/RTC/PlainTransport.cpp index 59d2b154fe..76d2021185 100644 --- a/worker/src/RTC/PlainTransport.cpp +++ b/worker/src/RTC/PlainTransport.cpp @@ -4,6 +4,7 @@ #include "RTC/PlainTransport.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" +#include "RTC/PortManager.hpp" #include "RTC/SCTP/packet/Packet.hpp" #include "Settings.hpp" #include "Utils.hpp" @@ -151,7 +152,7 @@ namespace RTC { if (this->listenInfo.portRange.min != 0 && this->listenInfo.portRange.max != 0) { - RTC::PortManager::PortRangeKey portRangeKey{}; + RTC::PortManager::PortRangeKey portRangeKey; this->udpSocket = new RTC::UdpSocket( this, @@ -171,7 +172,7 @@ namespace RTC // required. else { - RTC::PortManager::PortRangeKey portRangeKey{}; + RTC::PortManager::PortRangeKey portRangeKey; this->udpSocket = new RTC::UdpSocket( this, @@ -198,7 +199,7 @@ namespace RTC { if (this->rtcpListenInfo.portRange.min != 0 && this->rtcpListenInfo.portRange.max != 0) { - RTC::PortManager::PortRangeKey portRangeKey{}; + RTC::PortManager::PortRangeKey portRangeKey; this->rtcpUdpSocket = new RTC::UdpSocket( this, @@ -218,7 +219,7 @@ namespace RTC // required. else { - RTC::PortManager::PortRangeKey portRangeKey{}; + RTC::PortManager::PortRangeKey portRangeKey; this->rtcpUdpSocket = new RTC::UdpSocket( this, diff --git a/worker/src/RTC/PortManager.cpp b/worker/src/RTC/PortManager.cpp index 42a153751d..5f09634dee 100644 --- a/worker/src/RTC/PortManager.cpp +++ b/worker/src/RTC/PortManager.cpp @@ -36,132 +36,7 @@ namespace RTC map PortManager::mapPortRanges; - /* PortRangeKey methods. */ - - PortManager::PortRangeKey::PortRangeKey( - Protocol protocol, const sockaddr_storage& bindAddr, uint16_t minPort, uint16_t maxPort) - : protocol(protocol), bindAddr(bindAddr), minPort(minPort), maxPort(maxPort) - { - // sockaddr_storage is padded; the unused tail bytes are caller-controlled. - // operator== inspects only the meaningful address bytes (sin_addr / - // sin6_addr) so padding does not affect equality, and the hash function - // hashes the same exact fields, so two structurally-equal keys always - // produce the same hash regardless of how the caller zero-initialized. - } - - bool PortManager::PortRangeKey::operator==(const PortRangeKey& other) const noexcept - { - if (this->protocol != other.protocol) - { - return false; - } - if (this->minPort != other.minPort) - { - return false; - } - if (this->maxPort != other.maxPort) - { - return false; - } - if (this->bindAddr.ss_family != other.bindAddr.ss_family) - { - return false; - } - - switch (this->bindAddr.ss_family) - { - case AF_INET: - { - const auto* a = reinterpret_cast(&this->bindAddr); - const auto* b = reinterpret_cast(&other.bindAddr); - - return a->sin_addr.s_addr == b->sin_addr.s_addr; - } - - case AF_INET6: - { - const auto* a = reinterpret_cast(&this->bindAddr); - const auto* b = reinterpret_cast(&other.bindAddr); - - return std::memcmp(&a->sin6_addr, &b->sin6_addr, sizeof(in6_addr)) == 0; - } - - default: - { - // Unknown family; treat as not equal to avoid accidental merge. - return false; - } - } - } - - // Hash function. Uses ankerl::unordered_dense per-field hashes combined with - // the standard boost-style hash_combine seed mixer. We deliberately do NOT - // hash the raw sockaddr_storage bytes (padding is caller-controlled and - // would cause structurally-equal keys to hash differently); instead we hash - // the same fields that operator== inspects. - size_t PortManager::PortRangeKeyHash::operator()(const PortRangeKey& key) const noexcept - { - const auto protocolBits = static_cast(key.protocol); - const auto familyBits = static_cast(key.bindAddr.ss_family); - - auto hashCombine = [](size_t& seed, size_t value) - { - seed ^= value + 0x9e3779b9 + (seed << 6) + (seed >> 2); - }; - - size_t seed = 0; - - switch (key.bindAddr.ss_family) - { - case AF_INET: - { - const auto* in = reinterpret_cast(&key.bindAddr); - - hashCombine(seed, ankerl::unordered_dense::hash{}(protocolBits)); - hashCombine(seed, ankerl::unordered_dense::hash{}(familyBits)); - hashCombine(seed, ankerl::unordered_dense::hash{}(in->sin_addr.s_addr)); - hashCombine(seed, ankerl::unordered_dense::hash{}(key.minPort)); - hashCombine(seed, ankerl::unordered_dense::hash{}(key.maxPort)); - - break; - } - - case AF_INET6: - { - const auto* in6 = reinterpret_cast(&key.bindAddr); - const auto* addr = in6->sin6_addr.s6_addr; - - uint64_t hi; - uint64_t lo; - - std::memcpy(&hi, addr, sizeof(uint64_t)); - std::memcpy(&lo, addr + sizeof(uint64_t), sizeof(uint64_t)); - - hashCombine(seed, ankerl::unordered_dense::hash{}(protocolBits)); - hashCombine(seed, ankerl::unordered_dense::hash{}(familyBits)); - hashCombine(seed, ankerl::unordered_dense::hash{}(hi)); - hashCombine(seed, ankerl::unordered_dense::hash{}(lo)); - hashCombine(seed, ankerl::unordered_dense::hash{}(key.minPort)); - hashCombine(seed, ankerl::unordered_dense::hash{}(key.maxPort)); - - break; - } - - default: - { - hashCombine(seed, ankerl::unordered_dense::hash{}(protocolBits)); - hashCombine(seed, ankerl::unordered_dense::hash{}(familyBits)); - hashCombine(seed, ankerl::unordered_dense::hash{}(key.minPort)); - hashCombine(seed, ankerl::unordered_dense::hash{}(key.maxPort)); - - break; - } - } - - return seed; - } - - /* Class methods. */ + /* PortManager class methods. */ uv_handle_t* PortManager::Bind( Protocol protocol, std::string& ip, uint16_t port, RTC::Transport::SocketFlags& flags) @@ -199,7 +74,8 @@ namespace RTC { case AF_INET: { - err = uv_ip4_addr(ip.c_str(), 0, reinterpret_cast(&bindAddr)); + err = uv_ip4_addr( + ip.c_str(), 0, reinterpret_cast(std::addressof(bindAddr))); if (err != 0) { @@ -211,7 +87,8 @@ namespace RTC case AF_INET6: { - err = uv_ip6_addr(ip.c_str(), 0, reinterpret_cast(&bindAddr)); + err = uv_ip6_addr( + ip.c_str(), 0, reinterpret_cast(std::addressof(bindAddr))); if (err != 0) { @@ -233,14 +110,14 @@ namespace RTC { case AF_INET: { - (reinterpret_cast(&bindAddr))->sin_port = htons(port); + (reinterpret_cast(std::addressof(bindAddr)))->sin_port = htons(port); break; } case AF_INET6: { - (reinterpret_cast(&bindAddr))->sin6_port = htons(port); + (reinterpret_cast(std::addressof(bindAddr)))->sin6_port = htons(port); break; } @@ -303,7 +180,7 @@ namespace RTC { err = uv_udp_bind( reinterpret_cast(uvHandle), - reinterpret_cast(&bindAddr), + reinterpret_cast(std::addressof(bindAddr)), bitFlags); if (err != 0) @@ -326,7 +203,7 @@ namespace RTC { err = uv_tcp_bind( reinterpret_cast(uvHandle), - reinterpret_cast(&bindAddr), + reinterpret_cast(std::addressof(bindAddr)), bitFlags); if (err != 0) @@ -416,7 +293,8 @@ namespace RTC { case AF_INET: { - err = uv_ip4_addr(ip.c_str(), 0, reinterpret_cast(&bindAddr)); + err = uv_ip4_addr( + ip.c_str(), 0, reinterpret_cast(std::addressof(bindAddr))); if (err != 0) { @@ -428,7 +306,8 @@ namespace RTC case AF_INET6: { - err = uv_ip6_addr(ip.c_str(), 0, reinterpret_cast(&bindAddr)); + err = uv_ip6_addr( + ip.c_str(), 0, reinterpret_cast(std::addressof(bindAddr))); if (err != 0) { @@ -513,14 +392,14 @@ namespace RTC { case AF_INET: { - (reinterpret_cast(&bindAddr))->sin_port = htons(port); + (reinterpret_cast(std::addressof(bindAddr)))->sin_port = htons(port); break; } case AF_INET6: { - (reinterpret_cast(&bindAddr))->sin6_port = htons(port); + (reinterpret_cast(std::addressof(bindAddr)))->sin6_port = htons(port); break; } @@ -583,7 +462,7 @@ namespace RTC { err = uv_udp_bind( reinterpret_cast(uvHandle), - reinterpret_cast(&bindAddr), + reinterpret_cast(std::addressof(bindAddr)), bitFlags); if (err != 0) @@ -605,7 +484,7 @@ namespace RTC { err = uv_tcp_bind( reinterpret_cast(uvHandle), - reinterpret_cast(&bindAddr), + reinterpret_cast(std::addressof(bindAddr)), bitFlags); if (err != 0) @@ -764,12 +643,11 @@ namespace RTC { MS_DUMP_CLEAN(indentation, ""); - for (auto& kv : PortManager::mapPortRanges) + for (const auto& kv : PortManager::mapPortRanges) { - const auto& key = kv.first; - const auto& portRange = kv.second; - const char* protocolStr = - (key.protocol == Protocol::UDP) ? "udp" : "tcp"; + const auto& key = kv.first; + const auto& portRange = kv.second; + const char* protocolStr = (key.protocol == Protocol::UDP) ? "udp" : "tcp"; MS_DUMP_CLEAN(indentation + 1, ""); MS_DUMP_CLEAN(indentation + 1, " protocol: %s", protocolStr); @@ -788,7 +666,7 @@ namespace RTC { MS_TRACE(); - auto it = PortManager::mapPortRanges.find(key); + const auto it = PortManager::mapPortRanges.find(key); // If the key is already handled, return its port range. if (it != PortManager::mapPortRanges.end()) @@ -800,9 +678,9 @@ namespace RTC const uint16_t numPorts = maxPort - minPort + 1; - // Emplace a new vector filled with numPorts false values, meaning that + // Emplace a new vector filled with `numPorts` false values, meaning that // all ports are available. - auto pair = PortManager::mapPortRanges.emplace( + const auto pair = PortManager::mapPortRanges.emplace( std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(numPorts, minPort)); // pair.first is an iterator to the inserted value. @@ -817,7 +695,7 @@ namespace RTC uint8_t bitFlags{ 0b00000000 }; - // Ignore ipv6Only in IPv4, otherwise libuv will throw. + // Ignore `ipv6Only` in IPv4, otherwise libuv will throw. if (flags.ipv6Only && family == AF_INET6) { switch (protocol) @@ -838,7 +716,7 @@ namespace RTC } } - // Ignore udpReusePort in TCP, otherwise libuv will throw. + // Ignore `udpReusePort` in TCP, otherwise libuv will throw. if (flags.udpReusePort && protocol == Protocol::UDP) { bitFlags |= UV_UDP_REUSEADDR; @@ -846,4 +724,133 @@ namespace RTC return bitFlags; } + + /* PortRangeKey instance methods. */ + + PortManager::PortRangeKey::PortRangeKey( + Protocol protocol, const sockaddr_storage& bindAddr, uint16_t minPort, uint16_t maxPort) + : protocol(protocol), bindAddr(bindAddr), minPort(minPort), maxPort(maxPort) + { + MS_TRACE(); + + // `sockaddr_storage` is padded; the unused tail bytes are caller-controlled. + // `operator==` inspects only the meaningful address bytes (`sin_addr` / + // `sin6_addr`) so padding does not affect equality, and the hash function + // hashes the same exact fields, so two structurally-equal keys always + // produce the same hash regardless of how the caller zero-initialized. + } + + bool PortManager::PortRangeKey::operator==(const PortRangeKey& other) const noexcept + { + MS_TRACE(); + + if (this->protocol != other.protocol) + { + return false; + } + else if (this->minPort != other.minPort) + { + return false; + } + else if (this->maxPort != other.maxPort) + { + return false; + } + else if (this->bindAddr.ss_family != other.bindAddr.ss_family) + { + return false; + } + + switch (this->bindAddr.ss_family) + { + case AF_INET: + { + const auto* a = reinterpret_cast(std::addressof(this->bindAddr)); + const auto* b = reinterpret_cast(std::addressof(other.bindAddr)); + + return a->sin_addr.s_addr == b->sin_addr.s_addr; + } + + case AF_INET6: + { + const auto* a = reinterpret_cast(std::addressof(this->bindAddr)); + const auto* b = reinterpret_cast(std::addressof(other.bindAddr)); + + return std::memcmp( + std::addressof(a->sin6_addr), std::addressof(b->sin6_addr), sizeof(in6_addr)) == 0; + } + + default: + { + // Unknown family; treat as not equal to avoid accidental merge. + return false; + } + } + } + + /* PortRangeKeyHash instance methods. */ + + size_t PortManager::PortRangeKeyHash::operator()(const PortRangeKey& key) const noexcept + { + MS_TRACE(); + + const auto protocolBits = static_cast(key.protocol); + const auto familyBits = static_cast(key.bindAddr.ss_family); + + auto hashCombine = [](size_t& seed, size_t value) + { + seed ^= value + 0x9e3779b9 + (seed << 6) + (seed >> 2); + }; + + size_t seed = 0; + + switch (key.bindAddr.ss_family) + { + case AF_INET: + { + const auto* in = reinterpret_cast(std::addressof(key.bindAddr)); + + hashCombine(seed, ankerl::unordered_dense::hash{}(protocolBits)); + hashCombine(seed, ankerl::unordered_dense::hash{}(familyBits)); + hashCombine(seed, ankerl::unordered_dense::hash{}(in->sin_addr.s_addr)); + hashCombine(seed, ankerl::unordered_dense::hash{}(key.minPort)); + hashCombine(seed, ankerl::unordered_dense::hash{}(key.maxPort)); + + break; + } + + case AF_INET6: + { + const auto* in6 = reinterpret_cast(std::addressof(key.bindAddr)); + const auto* addr = in6->sin6_addr.s6_addr; + + uint64_t hi; + uint64_t lo; + + std::memcpy(std::addressof(hi), addr, sizeof(uint64_t)); + std::memcpy(std::addressof(lo), addr + sizeof(uint64_t), sizeof(uint64_t)); + + hashCombine(seed, ankerl::unordered_dense::hash{}(protocolBits)); + hashCombine(seed, ankerl::unordered_dense::hash{}(familyBits)); + hashCombine(seed, ankerl::unordered_dense::hash{}(hi)); + hashCombine(seed, ankerl::unordered_dense::hash{}(lo)); + hashCombine(seed, ankerl::unordered_dense::hash{}(key.minPort)); + hashCombine(seed, ankerl::unordered_dense::hash{}(key.maxPort)); + + break; + } + + default: + { + hashCombine(seed, ankerl::unordered_dense::hash{}(protocolBits)); + hashCombine(seed, ankerl::unordered_dense::hash{}(familyBits)); + hashCombine(seed, ankerl::unordered_dense::hash{}(key.minPort)); + hashCombine(seed, ankerl::unordered_dense::hash{}(key.maxPort)); + + break; + } + } + + return seed; + } } // namespace RTC diff --git a/worker/src/RTC/WebRtcTransport.cpp b/worker/src/RTC/WebRtcTransport.cpp index 5d94b81715..1e3a55c997 100644 --- a/worker/src/RTC/WebRtcTransport.cpp +++ b/worker/src/RTC/WebRtcTransport.cpp @@ -5,6 +5,7 @@ #include "FBS/webRtcTransport.h" #include "Logger.hpp" #include "MediaSoupErrors.hpp" +#include "RTC/PortManager.hpp" #include "Settings.hpp" #include "Utils.hpp" #include // std::pow() @@ -81,7 +82,7 @@ namespace RTC if (listenInfo->portRange()->min() != 0 && listenInfo->portRange()->max() != 0) { - RTC::PortManager::PortRangeKey portRangeKey{}; + RTC::PortManager::PortRangeKey portRangeKey; udpSocket = new RTC::UdpSocket( this, @@ -100,7 +101,7 @@ namespace RTC // required. else { - RTC::PortManager::PortRangeKey portRangeKey{}; + RTC::PortManager::PortRangeKey portRangeKey; udpSocket = new RTC::UdpSocket( this, diff --git a/worker/test/src/RTC/TestNackGenerator.cpp b/worker/test/src/RTC/TestNackGenerator.cpp index 4fad4fcb78..271ed8953e 100644 --- a/worker/test/src/RTC/TestNackGenerator.cpp +++ b/worker/test/src/RTC/TestNackGenerator.cpp @@ -7,7 +7,7 @@ #include #include -SCENARIO("NACK generator", "[rtp][rtcp][nack]") +SCENARIO("NackGenerator generator", "[rtp][rtcp][nack]") { constexpr unsigned int SendNackDelay{ 0u }; // In ms. diff --git a/worker/test/src/RTC/TestPortManager.cpp b/worker/test/src/RTC/TestPortManager.cpp index 0de7c1622c..c402779a2e 100644 --- a/worker/test/src/RTC/TestPortManager.cpp +++ b/worker/test/src/RTC/TestPortManager.cpp @@ -1,64 +1,57 @@ #include "common.hpp" #include "RTC/PortManager.hpp" -#include #include #include -#include -#include -namespace +// Pre-fix issue #1805: The legacy `GeneratePortRangeHash()` mangled the IPv4 +// address with `(address >> 2) << 2`, so any two IPv4 addresses in the same +// /30 block produced the same `uint64_t` hash. The downstream +// `mapPortRanges.find(hash)` then treated them as the same PortRange and +// merged unrelated bindings. This scenario locks down the post-fix +// behavior: distinct tuples produce distinct keys, equal tuples produce equal +// keys. +SCENARIO("PortManager", "[rtc][portmanager]") { - // Helper: build an IPv4 sockaddr_storage from a dotted-quad string + port=0. - sockaddr_storage makeV4(const char* dottedQuad) + // Helper: build an IPv4 `sockaddr_storage` from a dotted-quad string + port=0. + auto makeV4 = [](const char* dottedQuad) { sockaddr_storage ss{}; - auto* in = reinterpret_cast(&ss); + auto* in = reinterpret_cast(std::addressof(ss)); + in->sin_family = AF_INET; in->sin_port = 0; + REQUIRE(inet_pton(AF_INET, dottedQuad, &in->sin_addr) == 1); return ss; - } + }; - // Helper: build an IPv6 sockaddr_storage from a textual address + port=0. - sockaddr_storage makeV6(const char* literal) + // Helper: build an IPv6 `sockaddr_storage` from a textual address + port=0. + auto makeV6 = [](const char* literal) { sockaddr_storage ss{}; - auto* in6 = reinterpret_cast(&ss); + auto* in6 = reinterpret_cast(std::addressof(ss)); + in6->sin6_family = AF_INET6; in6->sin6_port = 0; + REQUIRE(inet_pton(AF_INET6, literal, &in6->sin6_addr) == 1); return ss; - } -} // namespace - -SCENARIO("PortManager::PortRangeKey - exact-tuple semantics (issue #1805)", "[rtc][port-manager]") -{ - using RTC::PortManager; - - // Pre-fix issue #1805: the legacy GeneratePortRangeHash mangled the IPv4 - // address with `(address >> 2) << 2`, so any two IPv4 addresses in the - // same /30 block produced the same uint64_t hash. The downstream - // `mapPortRanges.find(hash)` then treated them as the same PortRange and - // merged unrelated bindings. This scenario locks down the post-fix - // behavior: distinct tuples produce distinct keys, equal tuples produce - // equal keys. + }; SECTION("identical tuples compare equal and hash equal") { const auto addr = makeV4("192.168.1.10"); - const PortManager::PortRangeKey a( - PortManager::Protocol::UDP, addr, 40000u, 40099u); - const PortManager::PortRangeKey b( - PortManager::Protocol::UDP, addr, 40000u, 40099u); + const RTC::PortManager::PortRangeKey ka(RTC::PortManager::Protocol::UDP, addr, 40000u, 40099u); + const RTC::PortManager::PortRangeKey kb(RTC::PortManager::Protocol::UDP, addr, 40000u, 40099u); - REQUIRE(a == b); - REQUIRE(PortManager::PortRangeKeyHash{}(a) == PortManager::PortRangeKeyHash{}(b)); + REQUIRE(ka == kb); + REQUIRE(RTC::PortManager::PortRangeKeyHash{}(ka) == RTC::PortManager::PortRangeKeyHash{}(kb)); } - SECTION("IPv4 addresses in the same /30 are NOT collapsed (was #1805)") + SECTION("IPv4 addresses in the same /30 are NOT collapsed") { // 192.168.1.0 / .1 / .2 / .3 all live in 192.168.1.0/30. The old // GeneratePortRangeHash dropped the bottom two bits of the address, @@ -68,10 +61,10 @@ SCENARIO("PortManager::PortRangeKey - exact-tuple semantics (issue #1805)", "[rt const auto a2 = makeV4("192.168.1.2"); const auto a3 = makeV4("192.168.1.3"); - const PortManager::PortRangeKey k0(PortManager::Protocol::UDP, a0, 40000u, 40099u); - const PortManager::PortRangeKey k1(PortManager::Protocol::UDP, a1, 40000u, 40099u); - const PortManager::PortRangeKey k2(PortManager::Protocol::UDP, a2, 40000u, 40099u); - const PortManager::PortRangeKey k3(PortManager::Protocol::UDP, a3, 40000u, 40099u); + const RTC::PortManager::PortRangeKey k0(RTC::PortManager::Protocol::UDP, a0, 40000u, 40099u); + const RTC::PortManager::PortRangeKey k1(RTC::PortManager::Protocol::UDP, a1, 40000u, 40099u); + const RTC::PortManager::PortRangeKey k2(RTC::PortManager::Protocol::UDP, a2, 40000u, 40099u); + const RTC::PortManager::PortRangeKey k3(RTC::PortManager::Protocol::UDP, a3, 40000u, 40099u); REQUIRE(k0 != k1); REQUIRE(k0 != k2); @@ -81,59 +74,52 @@ SCENARIO("PortManager::PortRangeKey - exact-tuple semantics (issue #1805)", "[rt REQUIRE(k2 != k3); } + // The old IPv6 hash folded `a[0] ^ a[1] ^ a[2] ^ a[3]` (4 x uint32_t) into + // 32 bits. Any two IPv6 addresses where the four 32-bit words XOR to the + // same value collided. Easiest collision constructor: swap two words. + // ::1 = 0000:0000:0000:0000:0000:0000:0000:0001 XOR-folds the same as + // ::1:0:0 (just word reorder). SECTION("IPv6 addresses that XOR-fold to the same value are NOT collapsed") { - // The old IPv6 hash folded `a[0] ^ a[1] ^ a[2] ^ a[3]` (4 x uint32_t) - // into 32 bits. Any two IPv6 addresses where the four 32-bit words - // XOR to the same value collided. Easiest collision constructor: swap - // two words. ::1 = 0000:0000:0000:0000:0000:0000:0000:0001 XOR-folds - // the same as ::1:0:0 (just word reorder). const auto a = makeV6("::1"); const auto b = makeV6("1::1:0:0:0"); - const PortManager::PortRangeKey ka(PortManager::Protocol::UDP, a, 40000u, 40099u); - const PortManager::PortRangeKey kb(PortManager::Protocol::UDP, b, 40000u, 40099u); + const RTC::PortManager::PortRangeKey ka(RTC::PortManager::Protocol::UDP, a, 40000u, 40099u); + const RTC::PortManager::PortRangeKey kb(RTC::PortManager::Protocol::UDP, b, 40000u, 40099u); REQUIRE(ka != kb); } - SECTION("Protocol differentiates the key (UDP/TCP on same address+range)") + SECTION("protocol differentiates the key (UDP/TCP on same address+range)") { const auto addr = makeV4("10.0.0.1"); - const PortManager::PortRangeKey udp( - PortManager::Protocol::UDP, addr, 40000u, 40099u); - const PortManager::PortRangeKey tcp( - PortManager::Protocol::TCP, addr, 40000u, 40099u); + const RTC::PortManager::PortRangeKey udp(RTC::PortManager::Protocol::UDP, addr, 40000u, 40099u); + const RTC::PortManager::PortRangeKey tcp(RTC::PortManager::Protocol::TCP, addr, 40000u, 40099u); REQUIRE(udp != tcp); } - SECTION("Port-range bounds differentiate the key") + SECTION("port-range bounds differentiate the key") { const auto addr = makeV4("10.0.0.1"); - const PortManager::PortRangeKey a( - PortManager::Protocol::UDP, addr, 40000u, 40099u); - const PortManager::PortRangeKey b( - PortManager::Protocol::UDP, addr, 40000u, 40100u); - const PortManager::PortRangeKey c( - PortManager::Protocol::UDP, addr, 40001u, 40099u); + const RTC::PortManager::PortRangeKey ka(RTC::PortManager::Protocol::UDP, addr, 40000u, 40099u); + const RTC::PortManager::PortRangeKey kb(RTC::PortManager::Protocol::UDP, addr, 40000u, 40100u); + const RTC::PortManager::PortRangeKey kc(RTC::PortManager::Protocol::UDP, addr, 40001u, 40099u); - REQUIRE(a != b); - REQUIRE(a != c); - REQUIRE(b != c); + REQUIRE(ka != kb); + REQUIRE(ka != kc); + REQUIRE(kb != kc); } - SECTION("Family differentiates the key") + SECTION("family differentiates the key") { - const auto v4 = makeV4("0.0.0.0"); - const auto v6 = makeV6("::"); + const auto v4 = makeV4("0.0.0.0"); + const auto v6 = makeV6("::"); - const PortManager::PortRangeKey k4( - PortManager::Protocol::UDP, v4, 40000u, 40099u); - const PortManager::PortRangeKey k6( - PortManager::Protocol::UDP, v6, 40000u, 40099u); + const RTC::PortManager::PortRangeKey k4(RTC::PortManager::Protocol::UDP, v4, 40000u, 40099u); + const RTC::PortManager::PortRangeKey k6(RTC::PortManager::Protocol::UDP, v6, 40000u, 40099u); REQUIRE(k4 != k6); } diff --git a/worker/test/src/RTC/TestTransportTuple.cpp b/worker/test/src/RTC/TestTransportTuple.cpp index 2bbcee7eaa..bf2f6a7170 100644 --- a/worker/test/src/RTC/TestTransportTuple.cpp +++ b/worker/test/src/RTC/TestTransportTuple.cpp @@ -1,4 +1,5 @@ #include "common.hpp" +#include "RTC/PortManager.hpp" #include "RTC/Transport.hpp" #include "RTC/TransportTuple.hpp" #include "RTC/UdpSocket.hpp" From a19aad8d8a7c4651218e73590f77705dc7ce1e24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C3=B1aki=20Baz=20Castillo?= Date: Mon, 1 Jun 2026 23:05:36 +0200 Subject: [PATCH 5/6] add CHANGELOG --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 564090d5c9..8fd3fdbf87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### NEXT +- `PortManager`: Replace `uint64_t` hash token with exact-tuple `PortRangeKey` ([PR #1812](https://github.com/versatica/mediasoup/pull/1812), by @999purple999). + ### 3.20.1 - Node: Make all public methods/getters that return an object/array, return a clone of that object/array ([PR #1811](https://github.com/versatica/mediasoup/pull/1811)). @@ -1196,7 +1198,7 @@ Migrate `npm-scripts.js` to `npm-scripts.mjs` (ES Module) ([PR #1093](https://gi ### 3.5.9 - `libwebrtc`: Apply patch by @sspanak and @Ivaka to avoid crash. Related issue: #357. -- `PortManager.cpp`: Do not use `UV_UDP_RECVMMSG` in Windows due to a bug in `libuv` 1.37.0. +- `PortManager`: Do not use `UV_UDP_RECVMMSG` in Windows due to a bug in `libuv` 1.37.0. - Update Node deps. ### 3.5.8 @@ -1290,7 +1292,7 @@ Migrate `npm-scripts.js` to `npm-scripts.mjs` (ES Module) ([PR #1093](https://gi ### 3.4.7 -- `PortManager.cpp`: Do not limit the number of failed `bind()` attempts to 20 since it does not work well in scenarios that launch tons of `Workers` with same port range. Instead iterate all ports in the range given to the Worker. +- `PortManager`: Do not limit the number of failed `bind()` attempts to 20 since it does not work well in scenarios that launch tons of `Workers` with same port range. Instead iterate all ports in the range given to the Worker. - Do not copy `catch.hpp` into `test/include/` but make the GYP `mediasoup-worker-test` target include the corresponding folder in `deps/catch`. ### 3.4.6 From 369e8b6cb2b6f6603b43201bd16dd8f9fe40e75a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C3=B1aki=20Baz=20Castillo?= Date: Mon, 1 Jun 2026 23:45:14 +0200 Subject: [PATCH 6/6] add @penguinol to CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fd3fdbf87..161beb1625 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ### NEXT -- `PortManager`: Replace `uint64_t` hash token with exact-tuple `PortRangeKey` ([PR #1812](https://github.com/versatica/mediasoup/pull/1812), by @999purple999). +- `PortManager`: Replace `uint64_t` hash token with exact-tuple `PortRangeKey` ([PR #1812](https://github.com/versatica/mediasoup/pull/1812), by @999purple999 and @penguinol). ### 3.20.1