diff --git a/Source/Core/Common/Network.cpp b/Source/Core/Common/Network.cpp index f79b4e6d84..9c161f1b26 100644 --- a/Source/Core/Common/Network.cpp +++ b/Source/Core/Common/Network.cpp @@ -17,15 +17,189 @@ #endif #include +#include #include "Common/BitUtils.h" #include "Common/CommonFuncs.h" #include "Common/Logging/Log.h" #include "Common/Random.h" #include "Common/StringUtil.h" +#include "Common/Swap.h" namespace Common { + +static constexpr auto GetMaskFromNetworkPrefixLength(u32 network_prefix_length) +{ + return (network_prefix_length == 0) ? + u32(0) : + u32(BigEndianValue(u32(-1) >> (32 - network_prefix_length))); +} + +u32 IPv4Port::GetIPAddressValue() const +{ + return std::bit_cast>(ip_address); +} + +u16 IPv4Port::GetPortValue() const +{ + return std::bit_cast>(port); +} + +bool IPv4PortRange::IsMatch(IPv4Port subject) const +{ + const u32 ip_u32 = subject.GetIPAddressValue(); + const u16 port_u16 = subject.GetPortValue(); + + return ip_u32 >= first.GetIPAddressValue() && ip_u32 <= last.GetIPAddressValue() && + port_u16 >= first.GetPortValue() && port_u16 <= last.GetPortValue(); +} + +std::string IPAddressToString(IPAddress ip_address) +{ + return fmt::format("{}", fmt::join(ip_address, ".")); +} + +std::optional StringToIPv4PortRange(std::string_view subject) +{ + // TODO: This could be optimized and improved a lot. + + auto read_pos = std::ranges::find_first_of(subject, std::string_view{"-/:"}); + + const auto first_ip_octets = SplitStringIntoArray<4>({subject.begin(), read_pos}, '.'); + + if (!first_ip_octets.has_value()) + return std::nullopt; // Wrong number of octets. + + std::optional result; + result.emplace(); + + for (u32 i = 0; i != result->first.ip_address.size(); ++i) + { + if (!TryParse(std::string((*first_ip_octets)[i]), &result->first.ip_address[i])) + return std::nullopt; // Bad octet. + } + + if (read_pos == subject.end()) + { + // Last IP and ports not specified. + result->last.ip_address = result->first.ip_address; + result->first.port = 0; + result->last.port = u16(-1); + return result; + } + + if (*read_pos == '-') + { + // Read last IP. + ++read_pos; + + const auto last_ip_end = std::ranges::find(read_pos, subject.end(), ':'); + const auto last_ip_octets = SplitString({read_pos, last_ip_end}, '.'); + + // Copy octets from first_ip when less than 4 are specified. + // FYI: This currently allows for syntax like: 10.0.0.1-7.255 + // I don't know if it's desirable or not to allow 2 or 3 octets on the last IP. + const std::size_t octet_share_count = 4 - last_ip_octets.size(); + + if (octet_share_count >= 4) + return std::nullopt; // Too many octets. + + std::copy_n(result->first.ip_address.begin(), octet_share_count, + result->last.ip_address.begin()); + + std::size_t i = octet_share_count; + for (auto&& octet_str : last_ip_octets) + { + if (!TryParse(octet_str, &result->last.ip_address[i])) + return std::nullopt; // Bad octet. + ++i; + } + + if (result->first.ip_address > result->last.ip_address) + return std::nullopt; // Reversed range. + + read_pos = last_ip_end; + } + else if (*read_pos == '/') + { + // Read network prefix length. + ++read_pos; + + u32 network_prefix_length{}; + const auto [parse_end, ec] = FromChars({read_pos, subject.end()}, network_prefix_length); + + if (ec != std::errc{} || network_prefix_length > 32) + return std::nullopt; // Bad network prefix. + + read_pos += parse_end - std::to_address(read_pos); // Working around MSVC iterators.. + + const auto net_mask = GetMaskFromNetworkPrefixLength(network_prefix_length); + const auto first_ip_address_u32 = std::bit_cast(result->first.ip_address) & net_mask; + result->first.ip_address = std::bit_cast(first_ip_address_u32); + result->last.ip_address = std::bit_cast(first_ip_address_u32 | ~net_mask); + } + else + { + // Last IP not specified. + result->last.ip_address = result->first.ip_address; + } + + if (read_pos == subject.end()) + { + // Ports not specified. + result->first.port = 0; + result->last.port = u16(-1); + return result; + } + + if (*read_pos != ':') + return std::nullopt; // Unexpected character. + + u16 first_port_u16{}; + { + // Read first port. + ++read_pos; + + const auto [parse_end, ec] = FromChars({read_pos, subject.end()}, first_port_u16); + if (ec != std::errc{}) + return std::nullopt; // Bad first port. + + result->first.port = std::bit_cast(Common::BigEndianValue(first_port_u16)); + + read_pos += parse_end - std::to_address(read_pos); // Working around MSVC iterators.. + } + + if (read_pos == subject.end()) + { + // Last port not specified. + result->last.port = result->first.port; + return result; + } + + if (*read_pos != '-') + return std::nullopt; // Unexpected character. + + u16 first_last_u16{}; + { + // Read last port. + ++read_pos; + + const auto [parse_end, ec] = FromChars({read_pos, subject.end()}, first_last_u16); + if (ec != std::errc{} || first_last_u16 < first_port_u16) + return std::nullopt; // Bad last port. + + result->last.port = std::bit_cast(Common::BigEndianValue(first_last_u16)); + + read_pos += parse_end - std::to_address(read_pos); // Working around MSVC iterators.. + } + + if (read_pos != subject.end()) + return std::nullopt; + + return result; +} + MACAddress GenerateMacAddress(const MACConsumer type) { constexpr std::array oui_bba{{0x00, 0x09, 0xbf}}; diff --git a/Source/Core/Common/Network.h b/Source/Core/Common/Network.h index 4ad18d3368..1939d10b2c 100644 --- a/Source/Core/Common/Network.h +++ b/Source/Core/Common/Network.h @@ -268,6 +268,29 @@ struct NetworkErrorState #endif }; +struct IPv4Port +{ + IPAddress ip_address; + u16 port; // Network byte order. + + // These convert to host byte order. + u32 GetIPAddressValue() const; + u16 GetPortValue() const; +}; + +struct IPv4PortRange +{ + IPv4Port first; + IPv4Port last; + + bool IsMatch(IPv4Port subject) const; +}; + +std::string IPAddressToString(IPAddress ip_address); + +// Syntax is: first_ip[-last_ip|/network_prefix_length][:first_port[-last_port]] +std::optional StringToIPv4PortRange(std::string_view subject); + MACAddress GenerateMacAddress(MACConsumer type); std::string MacAddressToString(const MACAddress& mac); diff --git a/Source/UnitTests/Common/StringUtilTest.cpp b/Source/UnitTests/Common/StringUtilTest.cpp index d7c783497a..c8bd5f4deb 100644 --- a/Source/UnitTests/Common/StringUtilTest.cpp +++ b/Source/UnitTests/Common/StringUtilTest.cpp @@ -6,6 +6,7 @@ #include #include +#include "Common/Network.h" #include "Common/StringUtil.h" #include "Common/Swap.h" @@ -282,6 +283,96 @@ TEST(StringUtil, SplitStringIntoArray) EXPECT_TRUE(SplitStringIntoArray<2>(subject4, '=').has_value()); } +TEST(StringUtil, StringToIPv4PortRange) +{ + constexpr auto parse = Common::StringToIPv4PortRange; + + constexpr Common::IPAddress ip_network = {192, 168, 0, 0}; + constexpr Common::IPAddress test_ip = {192, 168, 0, 13}; + constexpr Common::IPAddress last_ip = {192, 168, 0, 255}; + + EXPECT_FALSE(parse("").has_value()); + EXPECT_FALSE(parse("1.2.3").has_value()); + EXPECT_FALSE(parse("1.2.3.4-").has_value()); + EXPECT_FALSE(parse("1.2.3.4.5").has_value()); + EXPECT_FALSE(parse("1.2.3.256").has_value()); + EXPECT_FALSE(parse("1.2.3.4/33").has_value()); + EXPECT_FALSE(parse("1.2.3.4:65536").has_value()); + EXPECT_FALSE(parse("1.2.3.4-1.2.3.4.5").has_value()); + EXPECT_FALSE(parse("1.1.1.1:81-80").has_value()); + EXPECT_FALSE(parse("1.1.1.2-1.1.1.1").has_value()); + + EXPECT_TRUE(parse("1.2.3.4").has_value()); + EXPECT_TRUE(parse("1.2.3.4:80").has_value()); + EXPECT_TRUE(parse("1.2.3.4:80-81").has_value()); + EXPECT_TRUE(parse("1.2.3.4/16").has_value()); + EXPECT_TRUE(parse("1.2.3.4/16:80").has_value()); + EXPECT_TRUE(parse("1.2.3.4/16:80-81").has_value()); + EXPECT_TRUE(parse("1.2.3.4-10").has_value()); + EXPECT_TRUE(parse("1.2.3.4-10:80").has_value()); + EXPECT_TRUE(parse("1.2.3.4-10:80-81").has_value()); + + { + const auto simple_result = parse("192.168.0.13").value(); + EXPECT_EQ(simple_result.first.ip_address, test_ip); + EXPECT_EQ(simple_result.last.ip_address, test_ip); + EXPECT_EQ(simple_result.first.GetPortValue(), 0); + EXPECT_EQ(simple_result.last.GetPortValue(), u16(-1)); + } + + { + const auto port_result = parse("192.168.0.13:77").value(); + EXPECT_EQ(port_result.first.ip_address, test_ip); + EXPECT_EQ(port_result.last.ip_address, test_ip); + EXPECT_EQ(port_result.first.GetPortValue(), 77); + EXPECT_EQ(port_result.last.GetPortValue(), 77); + } + + { + const auto ip_range_result = parse("192.168.0.13-192.168.0.255:123").value(); + EXPECT_EQ(ip_range_result.first.ip_address, test_ip); + EXPECT_EQ(ip_range_result.last.ip_address, last_ip); + EXPECT_EQ(ip_range_result.first.GetPortValue(), 123); + EXPECT_EQ(ip_range_result.last.GetPortValue(), 123); + } + + { + const auto port_range_result = parse("192.168.29.151:5000-5010").value(); + EXPECT_EQ(port_range_result.first.GetPortValue(), 5000); + EXPECT_EQ(port_range_result.last.GetPortValue(), 5010); + EXPECT_TRUE(port_range_result.IsMatch(parse("192.168.29.151:5008")->first)); + EXPECT_FALSE(port_range_result.IsMatch(parse("127.0.0.1:5000")->first)); + } + + { + const auto ip_range_result = parse("192.168.0.13-255:123-999").value(); + EXPECT_EQ(ip_range_result.first.ip_address, test_ip); + EXPECT_EQ(ip_range_result.last.ip_address, last_ip); + EXPECT_EQ(ip_range_result.first.GetPortValue(), 123); + EXPECT_EQ(ip_range_result.last.GetPortValue(), 999); + } + + { + const auto ip_network_result = parse("192.168.0.13/24:123-999").value(); + EXPECT_EQ(ip_network_result.first.ip_address, ip_network); + EXPECT_EQ(ip_network_result.last.ip_address, last_ip); + EXPECT_EQ(ip_network_result.first.GetPortValue(), 123); + EXPECT_EQ(ip_network_result.last.GetPortValue(), 999); + } + + EXPECT_TRUE(parse("192.168.0.13/24")->IsMatch(parse("192.168.0.99")->first)); + EXPECT_FALSE(parse("192.168.0.13/24")->IsMatch(parse("192.168.1.99")->first)); + + EXPECT_FALSE(parse("192.168.0.13/32")->IsMatch(parse("192.168.0.99")->first)); + EXPECT_TRUE(parse("192.168.0.13/0")->IsMatch(parse("10.0.0.1")->first)); + + EXPECT_TRUE(parse("192.168.0.13")->IsMatch(parse("192.168.0.13:81")->first)); + EXPECT_FALSE(parse("192.168.0.13:80")->IsMatch(parse("192.168.0.13:81")->first)); + EXPECT_TRUE(parse("192.168.0.13:80")->IsMatch(parse("192.168.0.13:80")->first)); + EXPECT_FALSE(parse("192.168.0.13:80-81")->IsMatch(parse("192.168.0.14:81")->first)); + EXPECT_TRUE(parse("192.168.0.13-14:80-81")->IsMatch(parse("192.168.0.14:81")->first)); +} + TEST(StringUtil, CharacterEncodingConversion) { // wstring