fix(net): make new IPv6 sockets IPv6-only as FreeBSD does
On Linux, socket() and sceNetSocket() create AF_INET6 sockets in the
host's default dual-stack mode (net.ipv6.bindv6only is 0), so a wildcard
IPv6 bind also claims the IPv4 port. A title that keeps an IPv4 and an
IPv6 socket on the same port fails: with 0.0.0.0:P bound, binding [::]:P
returns EADDRINUSE (48), and an IPv6 socket bound to [::] also receives
IPv4 traffic from v4-mapped peers.
FreeBSD makes every new IPv6 socket IPv6-only. sys/netinet6/in6_proto.c:
VNET_DEFINE(int, ip6_v6only) = 1;
sys/netinet/in_pcb.c, in_pcballoc():
if (INP_SOCKAF(so) == AF_INET6) {
inp->inp_vflag |= INP_IPV6PROTO;
if (V_ip6_v6only)
inp->inp_flags |= IN6P_IPV6_V6ONLY;
}
and sys/netinet6/in6_pcb.c, in6_pcbbind(), looks for an IPv4 owner of
the port only when the flag is clear:
if ((inp->inp_flags & IN6P_IPV6_V6ONLY) == 0 &&
IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
socket() and sceNetSocket() now set IPV6_V6ONLY on every IPv6 socket
they create. Windows already defaults to it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
c36fd0a0eb
commit
98f6639e9e
|
|
@ -528,6 +528,13 @@ int APS5_VABI sceNetSocket(const char* name, int family, int type, int protocol)
|
|||
if (!initialize_sockets()) return fail(5);
|
||||
const NativeSocket native = ::socket(family == NET_AF_INET ? AF_INET : AF_INET6, type, protocol);
|
||||
if (native == INVALID_NATIVE_SOCKET) return fail(native_error());
|
||||
const int ipv6_only = 1;
|
||||
if (family == NET_AF_INET6 &&
|
||||
::setsockopt(native, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<const char*>(&ipv6_only), sizeof(ipv6_only)) != 0) {
|
||||
const int error = native_error();
|
||||
close_socket(native);
|
||||
return fail(error);
|
||||
}
|
||||
std::shared_ptr<NativeSocketHandle> handle;
|
||||
try {
|
||||
handle = std::make_shared<NativeSocketHandle>(native);
|
||||
|
|
|
|||
|
|
@ -506,6 +506,9 @@ int APS5_VABI socket_nid_postfix(int family, int type, int protocol) {
|
|||
const auto native = ::socket(family == 2 ? AF_INET : AF_INET6, nativeType, protocol);
|
||||
if (native == Invalid) return Fail(NativeError());
|
||||
Socket guard(native, family, plainType);
|
||||
const int ipv6Only = 1;
|
||||
if (family == 28 && ::setsockopt(native, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<const char*>(&ipv6Only), sizeof(ipv6Only)))
|
||||
return Fail(NativeError());
|
||||
try {
|
||||
auto socket = std::make_shared<Socket>(native, family, plainType);
|
||||
guard.value = Invalid;
|
||||
|
|
|
|||
|
|
@ -338,6 +338,25 @@ static void CheckUnspecifiedIpv6() {
|
|||
Require(sceNetSocketClose(receiver) == 0);
|
||||
}
|
||||
|
||||
static void CheckIpv6SocketsAreIpv6Only() {
|
||||
const int inet = sceNetSocket("ipv4-any", 2, 2, 17);
|
||||
Require(inet >= 0);
|
||||
std::array<std::uint8_t, 16> any{16, 2};
|
||||
Require(sceNetBind_nid_postfix(inet, any.data(), any.size()) == 0);
|
||||
std::uint32_t size = any.size();
|
||||
Require(sceNetGetsockname(inet, any.data(), &size) == 0 && size == any.size());
|
||||
const int inet6 = sceNetSocket("ipv6-any-same-port", 28, 2, 17);
|
||||
Require(inet6 >= 0);
|
||||
std::array<std::uint8_t, 28> any6{28, 28, any[2], any[3]};
|
||||
if (sceNetBind_nid_postfix(inet6, any6.data(), any6.size()) != 0) {
|
||||
std::fprintf(stderr, "sceNetBind of [::]:%d failed with errno %d while an IPv4 socket holds 0.0.0.0:%d\n",
|
||||
(any[2] << 8) | any[3], *sceNetErrnoLoc(), (any[2] << 8) | any[3]);
|
||||
std::abort();
|
||||
}
|
||||
Require(sceNetSocketClose(inet6) == 0);
|
||||
Require(sceNetSocketClose(inet) == 0);
|
||||
}
|
||||
|
||||
int main() {
|
||||
const auto epoch = std::chrono::steady_clock::time_point{};
|
||||
Require(KernelSocketSelect::DeadlineAfter(epoch, 2, 250000) == epoch + std::chrono::milliseconds(2250));
|
||||
|
|
@ -359,6 +378,7 @@ int main() {
|
|||
CheckAddressText(28, "1234:5678:9abc:def0:1234:5678:9abc:def0");
|
||||
CheckLinkLocalAllNodesIpv6();
|
||||
CheckUnspecifiedIpv6();
|
||||
if (!ipv6Unavailable) CheckIpv6SocketsAreIpv6Only();
|
||||
|
||||
const int closed = sceNetSocket("closed-by-kernel", 2, 2, 17);
|
||||
Require(closed >= 0);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
|
|
@ -47,8 +48,36 @@ static void StreamReceiveReportsNoSender() {
|
|||
for (const int descriptor : {client, accepted, listener}) Require(close_nid_postfix(descriptor) == 0);
|
||||
}
|
||||
|
||||
static void ListenersShareAPortAcrossFamilies() {
|
||||
const int inet = socket_nid_postfix(2, 1, 0);
|
||||
Require(inet >= 0);
|
||||
std::array<std::uint8_t, 16> any{16, 2};
|
||||
Require(bind_nid_postfix(inet, any.data(), any.size()) == 0 && listen_nid_postfix(inet, 4) == 0);
|
||||
std::uint32_t size = any.size();
|
||||
Require(getsockname_nid_postfix(inet, any.data(), &size) == 0 && size == any.size());
|
||||
const int inet6 = socket_nid_postfix(28, 1, 0);
|
||||
if (inet6 < 0) {
|
||||
Require(*__error_nid_postfix() == 47);
|
||||
Require(close_nid_postfix(inet) == 0);
|
||||
return;
|
||||
}
|
||||
std::array<std::uint8_t, 28> any6{28, 28, any[2], any[3]};
|
||||
if (bind_nid_postfix(inet6, any6.data(), any6.size()) != 0) {
|
||||
if (*__error_nid_postfix() != 49) {
|
||||
std::fprintf(stderr, "bind of [::]:%d failed with errno %d while an IPv4 listener holds 0.0.0.0:%d\n",
|
||||
(any[2] << 8) | any[3], *__error_nid_postfix(), (any[2] << 8) | any[3]);
|
||||
std::abort();
|
||||
}
|
||||
} else {
|
||||
Require(listen_nid_postfix(inet6, 4) == 0);
|
||||
}
|
||||
Require(close_nid_postfix(inet6) == 0);
|
||||
Require(close_nid_postfix(inet) == 0);
|
||||
}
|
||||
|
||||
int main() {
|
||||
StreamReceiveReportsNoSender();
|
||||
ListenersShareAPortAcrossFamilies();
|
||||
const int listener = socket_nid_postfix(2, 1, 6);
|
||||
Require(listener >= 0);
|
||||
std::array<std::uint8_t, 16> address{16, 2, 0, 0, 127, 0, 0, 1};
|
||||
|
|
|
|||
Loading…
Reference in New Issue