diff --git a/ChangeLog b/ChangeLog index 5ce6afcf7..6654157dc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -10,6 +10,7 @@ Bug fixes: - #6975 Fix server stop working - #6976 Fix windows builds - #6979 Manual config error in client mode +- #6983 Fix that mac client listens on ipv4 only Enhancements: - #6954 Move language selection to advanced section diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index f02ca2462..2e4494705 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -838,6 +838,15 @@ QString MainWindow::configFilename() QString MainWindow::address() const { QString i = appConfig().networkInterface(); + // if interface is IPv6 - ensure that ip is in square brackets + if (i.count(':') > 1) { + if(i[0] != '[') { + i.insert(0, '['); + } + if(i[i.size() - 1] != ']') { + i.push_back(']'); + } + } return (!i.isEmpty() ? i : "") + ":" + QString::number(appConfig().port()); } diff --git a/src/lib/arch/unix/ArchNetworkBSD.cpp b/src/lib/arch/unix/ArchNetworkBSD.cpp index 922743432..ff4fed1db 100644 --- a/src/lib/arch/unix/ArchNetworkBSD.cpp +++ b/src/lib/arch/unix/ArchNetworkBSD.cpp @@ -679,14 +679,27 @@ ArchNetworkBSD::nameToAddr(const std::string& name) char ipstr[INET6_ADDRSTRLEN]; struct addrinfo hints; struct addrinfo *p; + struct in6_addr serveraddr; int ret; memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; + hints.ai_flags = AI_NUMERICSERV; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + if (inet_pton(AF_INET, name.c_str(), &serveraddr) == 1) { + hints.ai_family = AF_INET; + hints.ai_flags |= AI_NUMERICHOST; + } + else if (inet_pton(AF_INET6, name.c_str(), &serveraddr) == 1) { + hints.ai_family = AF_INET6; + hints.ai_flags |= AI_NUMERICHOST; + } // done with static buffer ARCH->lockMutex(m_mutex); - if ((ret = getaddrinfo(name.c_str(), NULL, &hints, &p)) != 0) { + ret = getaddrinfo(name.c_str(), nullptr, &hints, &p); + if (ret != 0) { ARCH->unlockMutex(m_mutex); delete addr; throwNameError(ret); @@ -697,6 +710,7 @@ ArchNetworkBSD::nameToAddr(const std::string& name) } else { addr->m_len = (socklen_t)sizeof(struct sockaddr_in6); } + memcpy(&addr->m_addr, p->ai_addr, addr->m_len); freeaddrinfo(p); ARCH->unlockMutex(m_mutex); @@ -992,4 +1006,4 @@ ArchNetworkBSD::throwNameError(int err) default: throw XArchNetworkName(s_msg[4]); } -} \ No newline at end of file +} diff --git a/src/lib/net/NetworkAddress.cpp b/src/lib/net/NetworkAddress.cpp index 66654ec4d..5bbcbc403 100644 --- a/src/lib/net/NetworkAddress.cpp +++ b/src/lib/net/NetworkAddress.cpp @@ -22,6 +22,7 @@ #include "arch/Arch.h" #include "arch/XArch.h" +#include #include // @@ -62,46 +63,53 @@ NetworkAddress::NetworkAddress(const String& hostname, int port) : m_hostname(hostname), m_port(port) { - // check for port suffix - String::size_type i = m_hostname.rfind(':'); - if (i != String::npos && i + 1 < m_hostname.size()) { - // found a colon. see if it looks like an IPv6 address. - bool colonNotation = false; - bool dotNotation = false; - bool doubleColon = false; - for (String::size_type j = 0; j < i; ++j) { - if (m_hostname[j] == ':') { - colonNotation = true; - dotNotation = false; - if (m_hostname[j + 1] == ':') { - doubleColon = true; - } - } - else if (m_hostname[j] == '.' && colonNotation) { - dotNotation = true; - } + //detect internet protocol version with colom count + auto isColomPredicate = [](char c){return c == ':';}; + auto colomCount = std::count_if(m_hostname.begin(), m_hostname.end(), isColomPredicate); + + if(colomCount == 1) { + //ipv4 with port part + auto hostIt = m_hostname.find(':'); + try { + m_port = std::stoi(m_hostname.substr(hostIt + 1)); + } catch(...) { + throw XSocketAddress(XSocketAddress::kBadPort, m_hostname, m_port); } - // port suffix is ambiguous with IPv6 notation if there's - // a double colon and the end of the address is not in dot - // notation. in that case we assume it's not a port suffix. - // the user can replace the double colon with zeros to - // disambiguate. - if ((!doubleColon || dotNotation) && !colonNotation) { - // parse port from hostname - char* end; - const char* chostname = m_hostname.c_str(); - long suffixPort = strtol(chostname + i + 1, &end, 10); - if (end == chostname + i + 1 || *end != '\0') { - throw XSocketAddress(XSocketAddress::kBadPort, - m_hostname, m_port); + auto endHostnameIt = static_cast(hostIt); + m_hostname = m_hostname.substr(0, endHostnameIt > 0 ? endHostnameIt : 0); + } + else if (colomCount > 1) { + //ipv6 part + if (m_hostname[0] == '[') { + //ipv6 with port part + String portDelimeter = "]:"; + auto hostIt = m_hostname.find(portDelimeter); + + //bad syntax of ipv6 with port + if (hostIt == String::npos) { + throw XSocketAddress(XSocketAddress::kUnknown, m_hostname, m_port); } - // trim port from hostname - m_hostname.erase(i); + auto portSuffix = m_hostname.substr(hostIt + portDelimeter.size()); + //port is implied but omitted + if (portSuffix.empty()) { + throw XSocketAddress(XSocketAddress::kBadPort, m_hostname, m_port); + } + try { + m_port = std::stoi(portSuffix); + } catch(...) { + //port is not a number + throw XSocketAddress(XSocketAddress::kBadPort, m_hostname, m_port); + } - // save port - m_port = static_cast(suffixPort); + auto endHostnameIt = static_cast(hostIt) - 1; + m_hostname = m_hostname.substr(1, endHostnameIt > 0 ? endHostnameIt : 0); + } + + // ensure that ipv6 link-local adress ended with scope id + if (m_hostname.rfind("fe80:", 0) == 0 && m_hostname.find('%') == String::npos) { + throw XSocketAddress(XSocketAddress::kUnknown, m_hostname, m_port); } } diff --git a/src/test/unittests/server/ConfigTests.cpp b/src/test/unittests/server/ConfigTests.cpp index d661cbc5b..d38a89c19 100644 --- a/src/test/unittests/server/ConfigTests.cpp +++ b/src/test/unittests/server/ConfigTests.cpp @@ -16,6 +16,7 @@ */ #include "lib/server/Config.h" +#include "net/XSocket.h" #include "test/global/gtest.h" class OnlySystemFilter: public InputFilter::Condition { @@ -84,6 +85,59 @@ TEST(ServerConfigTests, serverconfig_will_deem_equal_configs_with_same_cell_name EXPECT_TRUE(b == a); } +TEST(NetworkAddress, hostname_valid_parsing) +{ + const int validPort = 24900; + const String portStr = std::to_string(validPort); + + //list of test cases. 1 param - hostname for parsing, 2 param - port, 3 param - expected hostname + const std::initializer_list> validTestCases = { + std::make_tuple(String("127.0.0.1"), validPort, "127.0.0.1"), + std::make_tuple(String("127.0.0.1:") + portStr, 0, "127.0.0.1"), + std::make_tuple(String(":") + portStr, 0, ""), + std::make_tuple(String("localhost"), validPort, "localhost"), + std::make_tuple(String("localhost:") + portStr, 0, "localhost"), + std::make_tuple(String(""), validPort, ""), + std::make_tuple(String("[::1]:") + portStr, 0, "::1"), + std::make_tuple(String("[fe80::a156:9f36:793:7bfb%14]:") + portStr, 0, "fe80::a156:9f36:793:7bfb%14"), + std::make_tuple(String("::1"), validPort, "::1"), + std::make_tuple(String("fe80::a156:9f36:793:7bfb%14"), validPort, "fe80::a156:9f36:793:7bfb%14"), + std::make_tuple(String("fe80:0000:0000:0000:a156:9f36:793:7bfb%14"), validPort, "fe80:0000:0000:0000:a156:9f36:793:7bfb%14"), + }; + + for (const auto &caseParams : validTestCases) { + NetworkAddress addr(std::get<0>(caseParams), std::get<1>(caseParams)); + addr.resolve(); + + EXPECT_TRUE(addr.getHostname() == std::get<2>(caseParams)); + EXPECT_TRUE(addr.getPort() == validPort); + EXPECT_TRUE(addr.getAddress() != nullptr); + } + + //list of non valid hostnames + const std::initializer_list nonValidTestCases = { + ":nonValidPort", + ":", + "[::1]:", + "[::1]:nonValidPort", + "fe80::1", + "[::1]:-1", + "[::1]:65536" + }; + + for (const auto &caseParam : nonValidTestCases) { + bool flag = false; + try { + NetworkAddress addr(caseParam, validPort); + } catch (const XSocketAddress&) { + + flag = true; + } + + EXPECT_TRUE(flag); + } +} + TEST(ServerConfigTests, serverconfig_will_deem_different_configs_with_same_cell_names_different_options) { Config a(nullptr);