mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-04-28 05:18:00 +03:00
Fix IPv6 support detection
Some checks are pending
Generate Translation Template / Generate Translation Template (push) Waiting to run
Build RPCS3 / RPCS3 Linux ubuntu-24.04 gcc (push) Waiting to run
Build RPCS3 / RPCS3 Linux ubuntu-24.04-arm clang (push) Waiting to run
Build RPCS3 / RPCS3 Linux ubuntu-24.04 clang (push) Waiting to run
Build RPCS3 / RPCS3 Windows (push) Waiting to run
Some checks are pending
Generate Translation Template / Generate Translation Template (push) Waiting to run
Build RPCS3 / RPCS3 Linux ubuntu-24.04 gcc (push) Waiting to run
Build RPCS3 / RPCS3 Linux ubuntu-24.04-arm clang (push) Waiting to run
Build RPCS3 / RPCS3 Linux ubuntu-24.04 clang (push) Waiting to run
Build RPCS3 / RPCS3 Windows (push) Waiting to run
This commit is contained in:
parent
ab269f6155
commit
89dea9bd92
8 changed files with 126 additions and 80 deletions
|
@ -18,7 +18,7 @@ bool send_packet_from_p2p_port_ipv4(const std::vector<u8>& data, const sockaddr_
|
||||||
{
|
{
|
||||||
auto& def_port = ::at32(nc.list_p2p_ports, SCE_NP_PORT);
|
auto& def_port = ::at32(nc.list_p2p_ports, SCE_NP_PORT);
|
||||||
|
|
||||||
if (def_port.is_ipv6)
|
if (np::is_ipv6_supported())
|
||||||
{
|
{
|
||||||
const auto addr6 = np::sockaddr_to_sockaddr6(addr);
|
const auto addr6 = np::sockaddr_to_sockaddr6(addr);
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ bool send_packet_from_p2p_port_ipv6(const std::vector<u8>& data, const sockaddr_
|
||||||
if (nc.list_p2p_ports.contains(SCE_NP_PORT))
|
if (nc.list_p2p_ports.contains(SCE_NP_PORT))
|
||||||
{
|
{
|
||||||
auto& def_port = ::at32(nc.list_p2p_ports, SCE_NP_PORT);
|
auto& def_port = ::at32(nc.list_p2p_ports, SCE_NP_PORT);
|
||||||
ensure(def_port.is_ipv6);
|
ensure(np::is_ipv6_supported());
|
||||||
|
|
||||||
if (::sendto(def_port.p2p_socket, reinterpret_cast<const char*>(data.data()), ::size32(data), 0, reinterpret_cast<const sockaddr*>(&addr), sizeof(sockaddr_in6)) == -1)
|
if (::sendto(def_port.p2p_socket, reinterpret_cast<const char*>(data.data()), ::size32(data), 0, reinterpret_cast<const sockaddr*>(&addr), sizeof(sockaddr_in6)) == -1)
|
||||||
{
|
{
|
||||||
|
|
|
@ -40,10 +40,10 @@ namespace sys_net_helpers
|
||||||
nt_p2p_port::nt_p2p_port(u16 port)
|
nt_p2p_port::nt_p2p_port(u16 port)
|
||||||
: port(port)
|
: port(port)
|
||||||
{
|
{
|
||||||
is_ipv6 = np::is_ipv6_supported();
|
const bool is_ipv6 = np::is_ipv6_supported();
|
||||||
|
|
||||||
// Creates and bind P2P Socket
|
// Creates and bind P2P Socket
|
||||||
p2p_socket = is_ipv6 ? ::socket(AF_INET6, SOCK_DGRAM, 0) : ::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
p2p_socket = ::socket(is_ipv6 ? AF_INET6 : AF_INET, SOCK_DGRAM, 0);
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
if (p2p_socket == INVALID_SOCKET)
|
if (p2p_socket == INVALID_SOCKET)
|
||||||
#else
|
#else
|
||||||
|
@ -145,13 +145,13 @@ bool nt_p2p_port::recv_data()
|
||||||
{
|
{
|
||||||
::sockaddr_storage native_addr{};
|
::sockaddr_storage native_addr{};
|
||||||
::socklen_t native_addrlen = sizeof(native_addr);
|
::socklen_t native_addrlen = sizeof(native_addr);
|
||||||
const auto recv_res = ::recvfrom(p2p_socket, reinterpret_cast<char*>(p2p_recv_data.data()), ::size32(p2p_recv_data), 0, reinterpret_cast<struct sockaddr*>(&native_addr), &native_addrlen);
|
const auto recv_res = ::recvfrom(p2p_socket, reinterpret_cast<char*>(p2p_recv_data.data()), ::size32(p2p_recv_data), 0, reinterpret_cast<struct sockaddr*>(&native_addr), &native_addrlen);
|
||||||
|
|
||||||
if (recv_res == -1)
|
if (recv_res == -1)
|
||||||
{
|
{
|
||||||
auto lerr = get_last_error(false);
|
auto lerr = get_last_error(false);
|
||||||
if (lerr != SYS_NET_EINPROGRESS && lerr != SYS_NET_EWOULDBLOCK)
|
if (lerr != SYS_NET_EINPROGRESS && lerr != SYS_NET_EWOULDBLOCK)
|
||||||
sys_net.error("Error recvfrom on %s P2P socket: %d", is_ipv6 ? "IPv6" : "IPv4", lerr);
|
sys_net.error("Error recvfrom on %s P2P socket: %d", np::is_ipv6_supported() ? "IPv6" : "IPv4", lerr);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -164,7 +164,7 @@ bool nt_p2p_port::recv_data()
|
||||||
|
|
||||||
u16 dst_vport = reinterpret_cast<le_t<u16>&>(p2p_recv_data[0]);
|
u16 dst_vport = reinterpret_cast<le_t<u16>&>(p2p_recv_data[0]);
|
||||||
|
|
||||||
if (is_ipv6)
|
if (np::is_ipv6_supported())
|
||||||
{
|
{
|
||||||
const auto* addr_ipv6 = reinterpret_cast<sockaddr_in6*>(&native_addr);
|
const auto* addr_ipv6 = reinterpret_cast<sockaddr_in6*>(&native_addr);
|
||||||
const auto addr_ipv4 = np::sockaddr6_to_sockaddr(*addr_ipv6);
|
const auto addr_ipv4 = np::sockaddr6_to_sockaddr(*addr_ipv6);
|
||||||
|
|
|
@ -48,8 +48,6 @@ struct nt_p2p_port
|
||||||
socket_type p2p_socket = 0;
|
socket_type p2p_socket = 0;
|
||||||
u16 port = 0;
|
u16 port = 0;
|
||||||
|
|
||||||
bool is_ipv6 = false;
|
|
||||||
|
|
||||||
shared_mutex bound_p2p_vports_mutex;
|
shared_mutex bound_p2p_vports_mutex;
|
||||||
// For DGRAM_P2P sockets (vport, sock_ids)
|
// For DGRAM_P2P sockets (vport, sock_ids)
|
||||||
std::map<u16, std::set<s32>> bound_p2p_vports{};
|
std::map<u16, std::set<s32>> bound_p2p_vports{};
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include "stdafx.h"
|
#include "stdafx.h"
|
||||||
|
#include "Emu/system_config.h"
|
||||||
#include "ip_address.h"
|
#include "ip_address.h"
|
||||||
#include "Utilities/StrFmt.h"
|
#include "Utilities/StrFmt.h"
|
||||||
#include "Emu/IdManager.h"
|
#include "Emu/IdManager.h"
|
||||||
|
@ -89,33 +90,54 @@ namespace np
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ipv6_support_state
|
||||||
|
{
|
||||||
|
ipv6_support_state() = default;
|
||||||
|
ipv6_support_state(const ipv6_support_state&) = delete;
|
||||||
|
ipv6_support_state& operator=(const ipv6_support_state&) = delete;
|
||||||
|
|
||||||
|
atomic_t<IPV6_SUPPORT> status = IPV6_SUPPORT::IPV6_UNKNOWN;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The conditions for IPv6 to be enabled are, in order:
|
||||||
|
// -IPv6 is not disabled in config
|
||||||
|
// -Internet config is Connected
|
||||||
|
// -PSN config is RPCN
|
||||||
|
// -RPCN host has an IPv6
|
||||||
|
// -Can connect to ipv6.google.com:413
|
||||||
bool is_ipv6_supported(std::optional<IPV6_SUPPORT> force_state)
|
bool is_ipv6_supported(std::optional<IPV6_SUPPORT> force_state)
|
||||||
{
|
{
|
||||||
static atomic_t<IPV6_SUPPORT> ipv6_status = IPV6_SUPPORT::IPV6_UNKNOWN;
|
auto& ipv6_support = g_fxo->get<ipv6_support_state>();
|
||||||
|
|
||||||
if (force_state)
|
if (force_state)
|
||||||
ipv6_status = *force_state;
|
ipv6_support.status = *force_state;
|
||||||
|
|
||||||
if (ipv6_status != IPV6_SUPPORT::IPV6_UNKNOWN)
|
if (ipv6_support.status != IPV6_SUPPORT::IPV6_UNKNOWN)
|
||||||
return ipv6_status == IPV6_SUPPORT::IPV6_SUPPORTED;
|
return ipv6_support.status == IPV6_SUPPORT::IPV6_SUPPORTED;
|
||||||
|
|
||||||
static shared_mutex mtx;
|
static shared_mutex mtx;
|
||||||
std::lock_guard lock(mtx);
|
std::lock_guard lock(mtx);
|
||||||
|
|
||||||
if (ipv6_status != IPV6_SUPPORT::IPV6_UNKNOWN)
|
if (ipv6_support.status != IPV6_SUPPORT::IPV6_UNKNOWN)
|
||||||
return ipv6_status == IPV6_SUPPORT::IPV6_SUPPORTED;
|
return ipv6_support.status == IPV6_SUPPORT::IPV6_SUPPORTED;
|
||||||
|
|
||||||
|
auto notice_and_disable = [&](std::string_view reason) -> bool
|
||||||
|
{
|
||||||
|
IPv6_log.notice("is_ipv6_supported(): disabled cause: %s", reason);
|
||||||
|
ipv6_support.status = IPV6_SUPPORT::IPV6_UNSUPPORTED;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
// IPv6 feature is only used by RPCN
|
// IPv6 feature is only used by RPCN
|
||||||
if (!g_cfg_rpcn.get_ipv6_support())
|
if (!g_cfg_rpcn.get_ipv6_support())
|
||||||
{
|
return notice_and_disable("force-disabled through config");
|
||||||
IPv6_log.notice("is_ipv6_supported(): disabled through config");
|
|
||||||
ipv6_status = IPV6_SUPPORT::IPV6_UNSUPPORTED;
|
if (g_cfg.net.net_active != np_internet_status::enabled || g_cfg.net.psn_status != np_psn_status::psn_rpcn)
|
||||||
return false;
|
return notice_and_disable("RPCN is disabled");
|
||||||
}
|
|
||||||
|
|
||||||
// We try to connect to ipv6.google.com:8080
|
|
||||||
addrinfo* addr_info{};
|
addrinfo* addr_info{};
|
||||||
socket_type socket_ipv6{};
|
socket_type socket_ipv6{};
|
||||||
|
const addrinfo hints{.ai_family = AF_INET6};
|
||||||
|
|
||||||
auto cleanup = [&]()
|
auto cleanup = [&]()
|
||||||
{
|
{
|
||||||
|
@ -126,31 +148,54 @@ namespace np
|
||||||
freeaddrinfo(addr_info);
|
freeaddrinfo(addr_info);
|
||||||
};
|
};
|
||||||
|
|
||||||
auto error_and_disable = [&](const char* message) -> bool
|
auto error_and_disable = [&](std::string_view message) -> bool
|
||||||
{
|
{
|
||||||
IPv6_log.error("is_ipv6_supported(): %s", message);
|
IPv6_log.error("is_ipv6_supported(): disabled cause: %s", message);
|
||||||
ipv6_status = IPV6_SUPPORT::IPV6_UNSUPPORTED;
|
ipv6_support.status = IPV6_SUPPORT::IPV6_UNSUPPORTED;
|
||||||
cleanup();
|
cleanup();
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
addrinfo hints{.ai_family = AF_INET6};
|
auto get_ipv6 = [](const addrinfo* addr_info) -> const addrinfo*
|
||||||
|
|
||||||
if (getaddrinfo("ipv6.google.com", nullptr, &hints, &addr_info))
|
|
||||||
return error_and_disable("Failed to resolve ipv6.google.com!");
|
|
||||||
|
|
||||||
addrinfo* found = addr_info;
|
|
||||||
|
|
||||||
while (found != nullptr)
|
|
||||||
{
|
{
|
||||||
if (found->ai_family == AF_INET6)
|
const addrinfo* found = addr_info;
|
||||||
break;
|
|
||||||
|
|
||||||
found = found->ai_next;
|
while (found != nullptr)
|
||||||
}
|
{
|
||||||
|
if (found->ai_family == AF_INET6)
|
||||||
|
break;
|
||||||
|
|
||||||
if (found == nullptr)
|
found = found->ai_next;
|
||||||
return error_and_disable("Failed to find IPv6 for ipv6.google.com");
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if RPCN has an IPv6 address
|
||||||
|
const auto parsed_host = parse_rpcn_host(g_cfg_rpcn.get_host());
|
||||||
|
|
||||||
|
if (!parsed_host)
|
||||||
|
return error_and_disable("failed to parse RPCN host");
|
||||||
|
|
||||||
|
const auto [hostname, _] = *parsed_host;
|
||||||
|
|
||||||
|
if (getaddrinfo(hostname.c_str(), nullptr, &hints, &addr_info))
|
||||||
|
return error_and_disable("failed to resolve RPCN host");
|
||||||
|
|
||||||
|
if (!get_ipv6(addr_info))
|
||||||
|
return error_and_disable("RPCN host doesn't support IPv6");
|
||||||
|
|
||||||
|
freeaddrinfo(addr_info);
|
||||||
|
addr_info = {};
|
||||||
|
|
||||||
|
// We try to connect to ipv6.google.com:8080
|
||||||
|
if (getaddrinfo("ipv6.google.com", nullptr, &hints, &addr_info))
|
||||||
|
return error_and_disable("failed to resolve ipv6.google.com");
|
||||||
|
|
||||||
|
const auto* google_ipv6_addr = get_ipv6(addr_info);
|
||||||
|
|
||||||
|
if (!google_ipv6_addr)
|
||||||
|
return error_and_disable("failed to find IPv6 for ipv6.google.com");
|
||||||
|
|
||||||
socket_type socket_or_err = ::socket(AF_INET6, SOCK_STREAM, 0);
|
socket_type socket_or_err = ::socket(AF_INET6, SOCK_STREAM, 0);
|
||||||
|
|
||||||
|
@ -162,7 +207,7 @@ namespace np
|
||||||
return error_and_disable("Failed to create IPv6 socket!");
|
return error_and_disable("Failed to create IPv6 socket!");
|
||||||
|
|
||||||
socket_ipv6 = socket_or_err;
|
socket_ipv6 = socket_or_err;
|
||||||
sockaddr_in6 ipv6_addr = *reinterpret_cast<const sockaddr_in6*>(found->ai_addr);
|
sockaddr_in6 ipv6_addr = *reinterpret_cast<const sockaddr_in6*>(google_ipv6_addr->ai_addr);
|
||||||
ipv6_addr.sin6_port = std::bit_cast<u16, be_t<u16>>(443);
|
ipv6_addr.sin6_port = std::bit_cast<u16, be_t<u16>>(443);
|
||||||
|
|
||||||
if (::connect(socket_ipv6, reinterpret_cast<const sockaddr*>(&ipv6_addr), sizeof(ipv6_addr)) != 0)
|
if (::connect(socket_ipv6, reinterpret_cast<const sockaddr*>(&ipv6_addr), sizeof(ipv6_addr)) != 0)
|
||||||
|
@ -171,7 +216,7 @@ namespace np
|
||||||
cleanup();
|
cleanup();
|
||||||
|
|
||||||
IPv6_log.success("Successfully tested IPv6 support!");
|
IPv6_log.success("Successfully tested IPv6 support!");
|
||||||
ipv6_status = IPV6_SUPPORT::IPV6_SUPPORTED;
|
ipv6_support.status = IPV6_SUPPORT::IPV6_SUPPORTED;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -970,33 +970,15 @@ namespace rpcn
|
||||||
|
|
||||||
state = rpcn_state::failure_no_failure;
|
state = rpcn_state::failure_no_failure;
|
||||||
|
|
||||||
if (host.empty())
|
const auto hostname_and_port = parse_rpcn_host(host);
|
||||||
|
|
||||||
|
if (!hostname_and_port)
|
||||||
{
|
{
|
||||||
rpcn_log.error("connect: RPCN host is empty!");
|
|
||||||
state = rpcn_state::failure_input;
|
state = rpcn_state::failure_input;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto splithost = fmt::split(host, {":"});
|
const auto [hostname, port] = *hostname_and_port;
|
||||||
if (splithost.size() != 1 && splithost.size() != 2)
|
|
||||||
{
|
|
||||||
rpcn_log.error("connect: RPCN host is invalid!");
|
|
||||||
state = rpcn_state::failure_input;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
u16 port = 31313;
|
|
||||||
|
|
||||||
if (splithost.size() == 2)
|
|
||||||
{
|
|
||||||
port = ::narrow<u16>(std::stoul(splithost[1]));
|
|
||||||
if (port == 0)
|
|
||||||
{
|
|
||||||
rpcn_log.error("connect: RPCN port is invalid!");
|
|
||||||
state = rpcn_state::failure_input;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
// Ensures both read & write threads are in waiting state
|
// Ensures both read & write threads are in waiting state
|
||||||
|
@ -1037,14 +1019,14 @@ namespace rpcn
|
||||||
|
|
||||||
addrinfo* addr_info{};
|
addrinfo* addr_info{};
|
||||||
|
|
||||||
if (getaddrinfo(splithost[0].c_str(), nullptr, nullptr, &addr_info))
|
if (getaddrinfo(hostname.c_str(), nullptr, nullptr, &addr_info))
|
||||||
{
|
{
|
||||||
rpcn_log.error("connect: Failed to getaddrinfo %s", host);
|
rpcn_log.error("connect: Failed to getaddrinfo %s", host);
|
||||||
state = rpcn_state::failure_resolve;
|
state = rpcn_state::failure_resolve;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool found_ipv4 = false, found_ipv6 = false;
|
bool found_ipv4 = false;
|
||||||
addrinfo* found = addr_info;
|
addrinfo* found = addr_info;
|
||||||
|
|
||||||
while (found != nullptr)
|
while (found != nullptr)
|
||||||
|
@ -1059,13 +1041,9 @@ namespace rpcn
|
||||||
}
|
}
|
||||||
case AF_INET6:
|
case AF_INET6:
|
||||||
{
|
{
|
||||||
if (np::is_ipv6_supported())
|
addr_rpcn_udp_ipv6.sin6_family = AF_INET6;
|
||||||
{
|
addr_rpcn_udp_ipv6.sin6_port = std::bit_cast<u16, be_t<u16>>(3657);
|
||||||
addr_rpcn_udp_ipv6.sin6_family = AF_INET6;
|
addr_rpcn_udp_ipv6.sin6_addr = reinterpret_cast<sockaddr_in6*>(found->ai_addr)->sin6_addr;
|
||||||
addr_rpcn_udp_ipv6.sin6_port = std::bit_cast<u16, be_t<u16>>(3657);
|
|
||||||
addr_rpcn_udp_ipv6.sin6_addr = reinterpret_cast<sockaddr_in6*>(found->ai_addr)->sin6_addr;
|
|
||||||
found_ipv6 = true;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: break;
|
default: break;
|
||||||
|
@ -1081,12 +1059,6 @@ namespace rpcn
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (np::is_ipv6_supported() && !found_ipv6)
|
|
||||||
{
|
|
||||||
rpcn_log.warning("IPv6 seems supported but no IPv6 could be found for the RPCN server, IPv6 is disabled!");
|
|
||||||
is_ipv6_supported(np::IPV6_SUPPORT::IPV6_UNSUPPORTED);
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(&addr_rpcn_udp_ipv4, &addr_rpcn, sizeof(addr_rpcn_udp_ipv4));
|
memcpy(&addr_rpcn_udp_ipv4, &addr_rpcn, sizeof(addr_rpcn_udp_ipv4));
|
||||||
addr_rpcn_udp_ipv4.sin_port = std::bit_cast<u16, be_t<u16>>(3657); // htons
|
addr_rpcn_udp_ipv4.sin_port = std::bit_cast<u16, be_t<u16>>(3657); // htons
|
||||||
|
|
||||||
|
|
|
@ -211,3 +211,33 @@ bool cfg_rpcn::del_host(std::string_view del_description, std::string_view del_h
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<std::pair<std::string, u16>> parse_rpcn_host(std::string_view host)
|
||||||
|
{
|
||||||
|
if (host.empty())
|
||||||
|
{
|
||||||
|
rpcn_log.error("RPCN host is empty!");
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto splithost = fmt::split(host, {":"});
|
||||||
|
if (splithost.size() != 1 && splithost.size() != 2)
|
||||||
|
{
|
||||||
|
rpcn_log.error("RPCN host is invalid!");
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
u16 port = 31313;
|
||||||
|
|
||||||
|
if (splithost.size() == 2)
|
||||||
|
{
|
||||||
|
port = ::narrow<u16>(std::stoul(splithost[1]));
|
||||||
|
if (port == 0)
|
||||||
|
{
|
||||||
|
rpcn_log.error("RPCN port is invalid!");
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_pair(std::move(splithost[0]), port);
|
||||||
|
}
|
||||||
|
|
|
@ -36,4 +36,6 @@ private:
|
||||||
void set_hosts(const std::vector<std::pair<std::string, std::string>>& vec_hosts);
|
void set_hosts(const std::vector<std::pair<std::string, std::string>>& vec_hosts);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::optional<std::pair<std::string, u16>> parse_rpcn_host(std::string_view host);
|
||||||
|
|
||||||
extern cfg_rpcn g_cfg_rpcn;
|
extern cfg_rpcn g_cfg_rpcn;
|
||||||
|
|
|
@ -200,9 +200,8 @@ rpcn_account_dialog::rpcn_account_dialog(QWidget* parent)
|
||||||
g_cfg_rpcn.set_host(host.toString().toStdString());
|
g_cfg_rpcn.set_host(host.toString().toStdString());
|
||||||
g_cfg_rpcn.save();
|
g_cfg_rpcn.save();
|
||||||
|
|
||||||
// Resets the state in case the support was limited by the RPCN server
|
// Resets the ipv6 support as it depends on availability of the feature on the server
|
||||||
if (!np::is_ipv6_supported())
|
np::is_ipv6_supported(np::IPV6_SUPPORT::IPV6_UNKNOWN);
|
||||||
np::is_ipv6_supported(np::IPV6_SUPPORT::IPV6_UNKNOWN);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(btn_add_server, &QAbstractButton::clicked, this, [this]()
|
connect(btn_add_server, &QAbstractButton::clicked, this, [this]()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue