mirror of
https://github.com/transmission/transmission.git
synced 2025-12-27 13:41:17 +00:00
feat: match IPv4-mapped addresses with IPv4 whitelist (#7523)
* feat: match IPv4-mapped addresses with IPv4 whitelist * test: for converting IPv4-mapped to native IPv4 * perf: avoid copying in `tr_wildmat()`
This commit is contained in:
@@ -798,6 +798,16 @@ int tr_address::compare(tr_address const& that) const noexcept // <=>
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<tr_address> tr_address::from_ipv4_mapped() const noexcept
|
||||
{
|
||||
if (!is_ipv4_mapped_address())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
return from_compact_ipv4(reinterpret_cast<std::byte const*>(&addr.addr6.s6_addr) + 12).first;
|
||||
}
|
||||
|
||||
// --- tr_socket_addrses
|
||||
|
||||
std::string tr_socket_address::display_name(tr_address const& address, tr_port port) noexcept
|
||||
|
||||
@@ -257,6 +257,8 @@ struct tr_address
|
||||
return is_ipv6() && IN6_IS_ADDR_LINKLOCAL(&addr.addr6);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<tr_address> from_ipv4_mapped() const noexcept;
|
||||
|
||||
tr_address_type type = NUM_TR_AF_INET_TYPES;
|
||||
union
|
||||
{
|
||||
|
||||
@@ -389,8 +389,20 @@ bool is_address_allowed(tr_rpc_server const* server, char const* address)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Convert IPv4-mapped address to IPv4 address
|
||||
// so that it can match with IPv4 whitelist entries
|
||||
auto native = std::string{};
|
||||
if (auto ipv4_mapped = tr_address::from_string(address); ipv4_mapped)
|
||||
{
|
||||
if (auto addr = ipv4_mapped->from_ipv4_mapped(); addr)
|
||||
{
|
||||
native = addr->display_name();
|
||||
}
|
||||
}
|
||||
auto const* const addr = std::empty(native) ? address : native.c_str();
|
||||
|
||||
auto const& src = server->whitelist_;
|
||||
return std::any_of(std::begin(src), std::end(src), [&address](auto const& s) { return tr_wildmat(address, s); });
|
||||
return std::any_of(std::begin(src), std::end(src), [&addr](auto const& s) { return tr_wildmat(addr, s.c_str()); });
|
||||
}
|
||||
|
||||
bool isIPAddressWithOptionalPort(char const* host)
|
||||
@@ -440,7 +452,11 @@ bool isHostnameAllowed(tr_rpc_server const* server, evhttp_request* const req)
|
||||
}
|
||||
|
||||
auto const& src = server->host_whitelist_;
|
||||
return std::any_of(std::begin(src), std::end(src), [&hostname](auto const& str) { return tr_wildmat(hostname, str); });
|
||||
auto const hostname_sz = tr_urlbuf{ hostname };
|
||||
return std::any_of(
|
||||
std::begin(src),
|
||||
std::end(src),
|
||||
[&hostname_sz](auto const& str) { return tr_wildmat(hostname_sz, str.c_str()); });
|
||||
}
|
||||
|
||||
bool test_session_id(tr_rpc_server const* server, evhttp_request* const req)
|
||||
|
||||
@@ -245,12 +245,12 @@ size_t tr_strv_to_buf(std::string_view src, char* buf, size_t buflen)
|
||||
return len;
|
||||
}
|
||||
|
||||
/* User-level routine. returns whether or not 'text' and 'p' matched */
|
||||
bool tr_wildmat(std::string_view text, std::string_view pattern)
|
||||
/* User-level routine. returns whether or not 'text' and 'pattern' matched */
|
||||
bool tr_wildmat(char const* text, char const* pattern)
|
||||
{
|
||||
// TODO(ckerr): replace wildmat with base/strings/pattern.cc
|
||||
// wildmat wants these to be zero-terminated.
|
||||
return pattern == "*"sv || DoMatch(std::string{ text }.c_str(), std::string{ pattern }.c_str()) > 0;
|
||||
return (pattern[0] == '*' && pattern[1] == '\0') || DoMatch(text, pattern) > 0;
|
||||
}
|
||||
|
||||
char const* tr_strerror(int errnum)
|
||||
|
||||
@@ -112,7 +112,7 @@ template<typename T>
|
||||
* @brief Rich Salz's classic implementation of shell-style pattern matching for `?`, `\`, `[]`, and `*` characters.
|
||||
* @return 1 if the pattern matches, 0 if it doesn't, or -1 if an error occurred
|
||||
*/
|
||||
[[nodiscard]] bool tr_wildmat(std::string_view text, std::string_view pattern);
|
||||
[[nodiscard]] bool tr_wildmat(char const* text, char const* pattern);
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]] constexpr bool tr_strv_contains(std::string_view sv, T key) noexcept // c++23
|
||||
|
||||
@@ -213,14 +213,16 @@ TEST_F(NetTest, isGlobalUnicastAddress)
|
||||
|
||||
TEST_F(NetTest, ipCompare)
|
||||
{
|
||||
static constexpr auto IpPairs = std::array{ std::tuple{ "223.18.245.229"sv, "8.8.8.8"sv, 1 },
|
||||
std::tuple{ "0.0.0.0"sv, "255.255.255.255"sv, -1 },
|
||||
std::tuple{ "8.8.8.8"sv, "8.8.8.8"sv, 0 },
|
||||
std::tuple{ "8.8.8.8"sv, "2001:0:0eab:dead::a0:abcd:4e"sv, -1 },
|
||||
std::tuple{ "2001:1890:1112:1::20"sv, "2001:0:0eab:dead::a0:abcd:4e"sv, 1 },
|
||||
std::tuple{ "2001:1890:1112:1::20"sv, "[2001:0:0eab:dead::a0:abcd:4e]"sv, 1 },
|
||||
std::tuple{ "2001:1890:1112:1::20"sv, "2001:1890:1112:1::20"sv, 0 },
|
||||
std::tuple{ "2001:1890:1112:1::20"sv, "[2001:1890:1112:1::20]"sv, 0 } };
|
||||
static constexpr auto IpPairs = std::array{
|
||||
std::tuple{ "223.18.245.229"sv, "8.8.8.8"sv, 1 },
|
||||
std::tuple{ "0.0.0.0"sv, "255.255.255.255"sv, -1 },
|
||||
std::tuple{ "8.8.8.8"sv, "8.8.8.8"sv, 0 },
|
||||
std::tuple{ "8.8.8.8"sv, "2001:0:0eab:dead::a0:abcd:4e"sv, -1 },
|
||||
std::tuple{ "2001:1890:1112:1::20"sv, "2001:0:0eab:dead::a0:abcd:4e"sv, 1 },
|
||||
std::tuple{ "2001:1890:1112:1::20"sv, "[2001:0:0eab:dead::a0:abcd:4e]"sv, 1 },
|
||||
std::tuple{ "2001:1890:1112:1::20"sv, "2001:1890:1112:1::20"sv, 0 },
|
||||
std::tuple{ "2001:1890:1112:1::20"sv, "[2001:1890:1112:1::20]"sv, 0 },
|
||||
};
|
||||
|
||||
for (auto const& [sv1, sv2, res] : IpPairs)
|
||||
{
|
||||
@@ -235,3 +237,34 @@ TEST_F(NetTest, ipCompare)
|
||||
EXPECT_EQ(ip1 == ip2, res == 0) << sv1 << ' ' << sv2;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(NetTest, IPv4MappedAddress)
|
||||
{
|
||||
static auto constexpr Tests = std::array<std::pair<std::string_view, std::string_view>, 14>{ {
|
||||
{ "::ffff:1.0.0.0"sv, "1.0.0.0"sv },
|
||||
{ "::ffff:10.0.0.0"sv, "10.0.0.0"sv },
|
||||
{ "::ffff:10.255.0.0"sv, "10.255.0.0"sv },
|
||||
{ "::ffff:10.255.0.255"sv, "10.255.0.255"sv },
|
||||
{ "::ffff:100.64.0.0"sv, "100.64.0.0"sv },
|
||||
{ "::ffff:100.128.0.0"sv, "100.128.0.0"sv },
|
||||
{ "::ffff:126.0.0.0"sv, "126.0.0.0"sv },
|
||||
{ "::ffff:127.0.0.0"sv, "127.0.0.0"sv },
|
||||
{ "::ffff:169.253.255.255"sv, "169.253.255.255"sv },
|
||||
{ "::ffff:169.254.0.0"sv, "169.254.0.0"sv },
|
||||
{ "::ffff:169.254.255.255"sv, "169.254.255.255"sv },
|
||||
{ "::ffff:169.255.0.0"sv, "169.255.0.0"sv },
|
||||
{ "::ffff:223.0.0.0"sv, "223.0.0.0"sv },
|
||||
{ "::ffff:224.0.0.0"sv, "224.0.0.0"sv },
|
||||
} };
|
||||
|
||||
for (auto const& [mapped_sv, native_sv] : Tests)
|
||||
{
|
||||
auto const mapped = tr_address::from_string(mapped_sv);
|
||||
ASSERT_TRUE(mapped);
|
||||
|
||||
auto const native = mapped->from_ipv4_mapped();
|
||||
ASSERT_TRUE(native);
|
||||
|
||||
EXPECT_EQ(native_sv, native->display_name());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user