Loading src/posix/udp.cpp +28 −10 Original line number Diff line number Diff line Loading @@ -56,16 +56,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define UDP_GRO 104 #endif // macOS lacks sendmmsg/recvmmsg entirely, so do OpenBSD, NetBSD and // DragonFly BSD -- calling them there doesn't compile. FreeBSD 11+ does // have the symbols, but sendmmsg() to an explicit (unconnected) peer // address has been observed there to fail a QUIC server's Retry/handshake // sends with EINVAL, even though the identical arguments work fine via // plain sendto() -- not something a Linux dev box can repro (it accepts // this call shape fine). Rather than special-case that one FreeBSD failure // mode, only Linux -- where this path is actually exercised and trusted -- // gets the real sendmmsg/recvmmsg path; every other platform, FreeBSD // included, uses the sendto/recvfrom fallback below. // macOS lacks sendmmsg/recvmmsg entirely, as do OpenBSD (pre-7.x), NetBSD // and DragonFly BSD -- calling them there doesn't compile. OpenBSD 7.9 and // FreeBSD 11+ do have the symbols, but the batch send/recv path here has // only ever been exercised end-to-end on Linux; a real BSD bug once // mistaken for an mmsg quirk turned out to be an unrelated msg_namelen // mismatch (see sockaddrLen() below), not anything wrong with sendmmsg() // itself. Until the mmsg path is specifically verified on a BSD, only // Linux gets it; everyone else uses the plain sendto/recvfrom fallback // below, which is simpler and has been the one actually proven correct // cross-platform. #if !defined(__linux__) #define NETPLUS_NO_MMSG 1 #endif Loading Loading @@ -396,6 +396,24 @@ void udp::probeGSOGRO(uint16_t segment_size) { } } // The real wire length of a peer address, by family — NOT // sizeof(sockaddr_storage). Linux's sendto()/sendmsg() tolerate an // oversized msg_namelen/addrlen (it just reads sizeof(sockaddr_in{,6}) // worth of bytes based on ss_family and ignores the rest), so passing // sizeof(sockaddr_storage) there silently "worked" by accident. BSD socket // implementations (confirmed on OpenBSD 7.9) validate the length strictly // against the address family and reject the mismatch with EINVAL — not // kqueue itself, which has nothing to do with sockaddr validation, but the // send()/sendmsg() syscall path underneath it. This was breaking every // send to a peer (QUIC Retry, handshake flight, and all post-handshake // application data via quic::flushBatch()'s _AddrLen) on BSD, while // epoll/Linux masked it completely. socklen_t sockaddrLen(const sockaddr_storage& addr) { if (addr.ss_family == AF_INET) return sizeof(struct sockaddr_in); if (addr.ss_family == AF_INET6) return sizeof(struct sockaddr_in6); return sizeof(sockaddr_storage); } // ============================================================================ // UDP Batched Send: sendmmsg + GSO when available // ============================================================================ Loading src/quic.cpp +0 −16 Original line number Diff line number Diff line Loading @@ -967,22 +967,6 @@ bool constantTimeEqual(const uint8_t* a, const uint8_t* b, size_t len) { return diff == 0; } // The real wire length of a peer address, by family — NOT // sizeof(sockaddr_storage). Linux's sendto()/sendmsg() tolerate an // oversized msg_namelen/addrlen (it just reads sizeof(sockaddr_in{,6}) // worth of bytes based on ss_family and ignores the rest), so passing // sizeof(sockaddr_storage) there silently "worked" by accident. FreeBSD's // kqueue-backed stack validates the length strictly and rejects the // mismatch with EINVAL — this was breaking every send to a peer (Retry, // handshake flight, and all post-handshake application data via // flushBatch()'s _AddrLen) on kqueue platforms, while epoll/Linux masked // it completely. socklen_t sockaddrLen(const sockaddr_storage& addr) { if (addr.ss_family == AF_INET) return sizeof(struct sockaddr_in); if (addr.ss_family == AF_INET6) return sizeof(struct sockaddr_in6); return sizeof(sockaddr_storage); } } // namespace std::array<uint8_t, 16> quic::computeRetryIntegrityTag( Loading src/socket.h +5 −0 Original line number Diff line number Diff line Loading @@ -324,6 +324,11 @@ namespace netplus { std::string _UxPath; }; // The real wire length of a peer address, by family — NOT // sizeof(sockaddr_storage). See udp.cpp's implementation for the full // cross-platform rationale. socklen_t sockaddrLen(const sockaddr_storage& addr); class udp : public socket { public: udp(); Loading src/windows/udp.cpp +11 −0 Original line number Diff line number Diff line Loading @@ -427,6 +427,17 @@ void udp::getAddress(std::string& addr) { } } // The real wire length of a peer address, by family — NOT // sizeof(sockaddr_storage). See posix/udp.cpp's implementation for the // full cross-platform rationale (a BSD sendto()/sendmsg() EINVAL this // fixed on the POSIX side); ws2tcpip.h provides the same sockaddr_in{,6} // layouts here, so the identical family switch applies. socklen_t sockaddrLen(const sockaddr_storage& addr) { if (addr.ss_family == AF_INET) return sizeof(struct sockaddr_in); if (addr.ss_family == AF_INET6) return sizeof(struct sockaddr_in6); return sizeof(sockaddr_storage); } // --- GSO/GRO stubs (not supported on Windows) --- void udp::probeGSOGRO(uint16_t segment_size) { Loading Loading
src/posix/udp.cpp +28 −10 Original line number Diff line number Diff line Loading @@ -56,16 +56,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define UDP_GRO 104 #endif // macOS lacks sendmmsg/recvmmsg entirely, so do OpenBSD, NetBSD and // DragonFly BSD -- calling them there doesn't compile. FreeBSD 11+ does // have the symbols, but sendmmsg() to an explicit (unconnected) peer // address has been observed there to fail a QUIC server's Retry/handshake // sends with EINVAL, even though the identical arguments work fine via // plain sendto() -- not something a Linux dev box can repro (it accepts // this call shape fine). Rather than special-case that one FreeBSD failure // mode, only Linux -- where this path is actually exercised and trusted -- // gets the real sendmmsg/recvmmsg path; every other platform, FreeBSD // included, uses the sendto/recvfrom fallback below. // macOS lacks sendmmsg/recvmmsg entirely, as do OpenBSD (pre-7.x), NetBSD // and DragonFly BSD -- calling them there doesn't compile. OpenBSD 7.9 and // FreeBSD 11+ do have the symbols, but the batch send/recv path here has // only ever been exercised end-to-end on Linux; a real BSD bug once // mistaken for an mmsg quirk turned out to be an unrelated msg_namelen // mismatch (see sockaddrLen() below), not anything wrong with sendmmsg() // itself. Until the mmsg path is specifically verified on a BSD, only // Linux gets it; everyone else uses the plain sendto/recvfrom fallback // below, which is simpler and has been the one actually proven correct // cross-platform. #if !defined(__linux__) #define NETPLUS_NO_MMSG 1 #endif Loading Loading @@ -396,6 +396,24 @@ void udp::probeGSOGRO(uint16_t segment_size) { } } // The real wire length of a peer address, by family — NOT // sizeof(sockaddr_storage). Linux's sendto()/sendmsg() tolerate an // oversized msg_namelen/addrlen (it just reads sizeof(sockaddr_in{,6}) // worth of bytes based on ss_family and ignores the rest), so passing // sizeof(sockaddr_storage) there silently "worked" by accident. BSD socket // implementations (confirmed on OpenBSD 7.9) validate the length strictly // against the address family and reject the mismatch with EINVAL — not // kqueue itself, which has nothing to do with sockaddr validation, but the // send()/sendmsg() syscall path underneath it. This was breaking every // send to a peer (QUIC Retry, handshake flight, and all post-handshake // application data via quic::flushBatch()'s _AddrLen) on BSD, while // epoll/Linux masked it completely. socklen_t sockaddrLen(const sockaddr_storage& addr) { if (addr.ss_family == AF_INET) return sizeof(struct sockaddr_in); if (addr.ss_family == AF_INET6) return sizeof(struct sockaddr_in6); return sizeof(sockaddr_storage); } // ============================================================================ // UDP Batched Send: sendmmsg + GSO when available // ============================================================================ Loading
src/quic.cpp +0 −16 Original line number Diff line number Diff line Loading @@ -967,22 +967,6 @@ bool constantTimeEqual(const uint8_t* a, const uint8_t* b, size_t len) { return diff == 0; } // The real wire length of a peer address, by family — NOT // sizeof(sockaddr_storage). Linux's sendto()/sendmsg() tolerate an // oversized msg_namelen/addrlen (it just reads sizeof(sockaddr_in{,6}) // worth of bytes based on ss_family and ignores the rest), so passing // sizeof(sockaddr_storage) there silently "worked" by accident. FreeBSD's // kqueue-backed stack validates the length strictly and rejects the // mismatch with EINVAL — this was breaking every send to a peer (Retry, // handshake flight, and all post-handshake application data via // flushBatch()'s _AddrLen) on kqueue platforms, while epoll/Linux masked // it completely. socklen_t sockaddrLen(const sockaddr_storage& addr) { if (addr.ss_family == AF_INET) return sizeof(struct sockaddr_in); if (addr.ss_family == AF_INET6) return sizeof(struct sockaddr_in6); return sizeof(sockaddr_storage); } } // namespace std::array<uint8_t, 16> quic::computeRetryIntegrityTag( Loading
src/socket.h +5 −0 Original line number Diff line number Diff line Loading @@ -324,6 +324,11 @@ namespace netplus { std::string _UxPath; }; // The real wire length of a peer address, by family — NOT // sizeof(sockaddr_storage). See udp.cpp's implementation for the full // cross-platform rationale. socklen_t sockaddrLen(const sockaddr_storage& addr); class udp : public socket { public: udp(); Loading
src/windows/udp.cpp +11 −0 Original line number Diff line number Diff line Loading @@ -427,6 +427,17 @@ void udp::getAddress(std::string& addr) { } } // The real wire length of a peer address, by family — NOT // sizeof(sockaddr_storage). See posix/udp.cpp's implementation for the // full cross-platform rationale (a BSD sendto()/sendmsg() EINVAL this // fixed on the POSIX side); ws2tcpip.h provides the same sockaddr_in{,6} // layouts here, so the identical family switch applies. socklen_t sockaddrLen(const sockaddr_storage& addr) { if (addr.ss_family == AF_INET) return sizeof(struct sockaddr_in); if (addr.ss_family == AF_INET6) return sizeof(struct sockaddr_in6); return sizeof(sockaddr_storage); } // --- GSO/GRO stubs (not supported on Windows) --- void udp::probeGSOGRO(uint16_t segment_size) { Loading