SYNERGY-885 mac client listens on ipv4 only but attempts to connect on ipv6 (#6983)

* SYNERGY-885 Mac client listens on ipv4 only but attempts to connect on ipv6
* Fix ipv6 port parsing
* Fix ipv6 server bind

* SYNERGY 885 Mac client listens on ipv4 only but attempts to connect on ipv6
* Add ipv6 scope checker

* SYNERGY-885 Mac client listens on ipv4 only but attempts to connect on ipv6
* Update changelog

* SYNERGY-885 Mac client listens on ipv4 only but attempts to connect on ipv6
* Refactor network address logic

* SYNERGY-885-Mac client listens on ipv4 only but attempts to connect on ipv6
* Build fix

* SYNERGY-885 Mac client listens on ipv4 only but attempts to connect on ipv6
* Fix code smells

* SYNERGY-885 Mac client listens on ipv4 only but attempts to connect on ipv6
* Fix empty ipv4 hostname

* SYNERGY-885 Mac client listens on ipv4 only but attempts to connect on ipv6
* Fix build

* SYNERGY-885 Mac client listens on ipv4 only but attempts to connect on ipv6
* Add test for new logic

* SYNERGY-885 Mac client listens on ipv4 only but attempts to connect on ipv6
* Upgrade network adress parser tests

* SYNERGY-885 Mac client listens on ipv4 only but attempts to connect on ipv6
* Fix sonar code smells in tests

* SYNERGY-885 Mac client listens on ipv4 only but attempts to connect on ipv6
* Fix code smells

* SYNERGY-885 Mac client listens on ipv4 only but attempts to connect on ipv6
*Fix comment

Co-authored-by: user <user@users-Mac-mini.local>
Co-authored-by: Andrii Batyiev <andrii-external@symless.com>
This commit is contained in:
Andrey Batyiev 2021-04-26 10:37:34 +03:00 committed by GitHub
parent 850fd20ff1
commit c1243deab9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 124 additions and 38 deletions

View file

@ -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

View file

@ -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());
}

View file

@ -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]);
}
}
}

View file

@ -22,6 +22,7 @@
#include "arch/Arch.h"
#include "arch/XArch.h"
#include <algorithm>
#include <cstdlib>
//
@ -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<int>(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<int>(suffixPort);
auto endHostnameIt = static_cast<int>(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);
}
}

View file

@ -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<std::tuple<String, int, String>> 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<String> 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);