From faaf2af43ba448454449f9aa3df240fd9af9e4d0 Mon Sep 17 00:00:00 2001 From: Jeffrey Ryan Date: Wed, 16 Nov 2022 15:53:15 -0600 Subject: [PATCH] p2p: fix exclusive node DNS resolution for certain hosts Fixes #8633. The function `append_net_address` did not parse hostname + port addresses (e.g. `bar:29080`) correctly if the hostname did not contain a `'.'` character. @vtnerd comments 1 clear up 2nd conditional statement --- src/net/parse.cpp | 8 +++++++- src/net/parse.h | 10 ++++++++++ src/p2p/net_node.inl | 16 +++------------- tests/unit_tests/net.cpp | 35 +++++++++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 14 deletions(-) diff --git a/src/net/parse.cpp b/src/net/parse.cpp index 1df6175b4..92be492a3 100644 --- a/src/net/parse.cpp +++ b/src/net/parse.cpp @@ -38,7 +38,7 @@ namespace net { void get_network_address_host_and_port(const std::string& address, std::string& host, std::string& port) { - // require ipv6 address format "[addr:addr:addr:...:addr]:port" + // If IPv6 address format with port "[addr:addr:addr:...:addr]:port" if (address.find(']') != std::string::npos) { host = address.substr(1, address.rfind(']') - 1); @@ -47,6 +47,12 @@ namespace net port = address.substr(address.rfind(':') + 1); } } + // Else if IPv6 address format without port e.g. "addr:addr:addr:...:addr" + else if (std::count(address.begin(), address.end(), ':') >= 2) + { + host = address; + } + // Else IPv4, Tor, I2P address or hostname else { host = address.substr(0, address.rfind(':')); diff --git a/src/net/parse.h b/src/net/parse.h index 648076d7b..6ece931c6 100644 --- a/src/net/parse.h +++ b/src/net/parse.h @@ -38,6 +38,16 @@ namespace net { + /*! + * \brief Takes a valid address string (IP, Tor, I2P, or DNS name) and splits it into host and port + * + * The host of an IPv6 addresses in the format "[x:x:..:x]:port" will have the braces stripped. + * For example, when the address is "[ffff::2023]", host will be set to "ffff::2023". + * + * \param address The address string one wants to split + * \param[out] host The host part of the address string. Is always set. + * \param[out] port The port part of the address string. Is only set when address string contains a port. + */ void get_network_address_host_and_port(const std::string& address, std::string& host, std::string& port); /*! diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index f33ce977d..b2486431a 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -645,20 +645,10 @@ namespace nodetool { using namespace boost::asio; - std::string host = addr; + // Split addr string into host string and port string + std::string host; std::string port = std::to_string(default_port); - size_t colon_pos = addr.find_last_of(':'); - size_t dot_pos = addr.find_last_of('.'); - size_t square_brace_pos = addr.find('['); - - // IPv6 will have colons regardless. IPv6 and IPv4 address:port will have a colon but also either a . or a [ - // as IPv6 addresses specified as address:port are to be specified as "[addr:addr:...:addr]:port" - // One may also specify an IPv6 address as simply "[addr:addr:...:addr]" without the port; in that case - // the square braces will be stripped here. - if ((std::string::npos != colon_pos && std::string::npos != dot_pos) || std::string::npos != square_brace_pos) - { - net::get_network_address_host_and_port(addr, host, port); - } + net::get_network_address_host_and_port(addr, host, port); MINFO("Resolving node address: host=" << host << ", port=" << port); io_service io_srv; diff --git a/tests/unit_tests/net.cpp b/tests/unit_tests/net.cpp index 838f1af37..af0f07db0 100644 --- a/tests/unit_tests/net.cpp +++ b/tests/unit_tests/net.cpp @@ -936,6 +936,41 @@ TEST(get_network_address, ipv4subnet) EXPECT_STREQ("12.34.0.0/16", address->str().c_str()); } +namespace +{ + void na_host_and_port_test(std::string addr, std::string exp_host, std::string exp_port) + { + std::string host{"xxxxx"}; + std::string port{"xxxxx"}; + net::get_network_address_host_and_port(addr, host, port); + EXPECT_EQ(exp_host, host); + EXPECT_EQ(exp_port, port); + } +} // anonymous namespace + +TEST(get_network_address_host_and_port, ipv4) +{ + na_host_and_port_test("9.9.9.9", "9.9.9.9", "xxxxx"); + na_host_and_port_test("9.9.9.9:18081", "9.9.9.9", "18081"); +} + +TEST(get_network_address_host_and_port, ipv6) +{ + na_host_and_port_test("::ffff", "::ffff", "xxxxx"); + na_host_and_port_test("[::ffff]", "::ffff", "xxxxx"); + na_host_and_port_test("[::ffff]:00231", "::ffff", "00231"); + na_host_and_port_test("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "xxxxx"); + na_host_and_port_test("[7777:7777:7777:7777:7777:7777:7777:7777]", "7777:7777:7777:7777:7777:7777:7777:7777", "xxxxx"); + na_host_and_port_test("[7777:7777:7777:7777:7777:7777:7777:7777]:48080", "7777:7777:7777:7777:7777:7777:7777:7777", "48080"); +} + +TEST(get_network_address_host_and_port, hostname) +{ + na_host_and_port_test("localhost", "localhost", "xxxxx"); + na_host_and_port_test("bar:29080", "bar", "29080"); // Issue https://github.com/monero-project/monero/issues/8633 + na_host_and_port_test("xmrchain.net:18081", "xmrchain.net", "18081"); +} + namespace { using stream_type = boost::asio::ip::tcp;