Correctly set IP header included flag for IPv6 sockets Turns out that raw IPv6 sockets require a different version of the IP_HDRINCL socket option. Manually confirmed tests pass with --config=lexan. PiperOrigin-RevId: 597653689
diff --git a/quiche/quic/core/io/socket.cc b/quiche/quic/core/io/socket.cc index 0e54102..ca28df2 100644 --- a/quiche/quic/core/io/socket.cc +++ b/quiche/quic/core/io/socket.cc
@@ -84,13 +84,6 @@ return SetSockOptInt(fd, SOL_SOCKET, SO_SNDBUF, static_cast<int>(size)); } -absl::Status SetIpHeaderIncluded(SocketFd fd, bool ip_header_included) { - QUICHE_DCHECK_NE(fd, kInvalidSocketFd); - - return SetSockOptInt(fd, IPPROTO_IP, IP_HDRINCL, - static_cast<int>(ip_header_included)); -} - absl::Status Connect(SocketFd fd, const QuicSocketAddress& peer_address) { QUICHE_DCHECK_NE(fd, kInvalidSocketFd); QUICHE_DCHECK(peer_address.IsInitialized());
diff --git a/quiche/quic/core/io/socket.h b/quiche/quic/core/io/socket.h index 57944be..dd49bd1 100644 --- a/quiche/quic/core/io/socket.h +++ b/quiche/quic/core/io/socket.h
@@ -78,7 +78,8 @@ // Only allowed for raw IP sockets. If set, sent data buffers include the IP // header. If not set, sent data buffers only contain the IP packet payload, and // the header will be generated. -absl::Status SetIpHeaderIncluded(SocketFd fd, bool ip_header_included); +absl::Status SetIpHeaderIncluded(SocketFd fd, IpAddressFamily address_family, + bool ip_header_included); // Connects socket `fd` to `peer_address`. Returns a status with // `absl::StatusCode::kUnavailable` iff the socket is non-blocking and the
diff --git a/quiche/quic/core/io/socket_posix.inc b/quiche/quic/core/io/socket_posix.inc index 9f45945..45e54e0 100644 --- a/quiche/quic/core/io/socket_posix.inc +++ b/quiche/quic/core/io/socket_posix.inc
@@ -19,6 +19,7 @@ #include "quiche/quic/core/quic_types.h" #include "quiche/quic/platform/api/quic_ip_address_family.h" #include "quiche/quic/platform/api/quic_socket_address.h" +#include "quiche/common/platform/api/quiche_bug_tracker.h" #include "quiche/common/platform/api/quiche_logging.h" // accept4() is a Linux-specific extension that is available in glibc 2.10+. @@ -213,6 +214,46 @@ } } +absl::Status SetIpHeaderIncluded(SocketFd fd, IpAddressFamily address_family, + bool ip_header_included) { + QUICHE_DCHECK_NE(fd, kInvalidSocketFd); + + int level; + int option; + switch (address_family) { + case IpAddressFamily::IP_V4: + level = IPPROTO_IP; + option = IP_HDRINCL; + break; + case IpAddressFamily::IP_V6: +#if defined(IPPROTO_IPV6) and defined(IPV6_HDRINCL) + level = IPPROTO_IPV6; + option = IPV6_HDRINCL; +#else + // If IPv6 options aren't defined, try with the IPv4 ones. + level = IPPROTO_IP; + option = IP_HDRINCL; +#endif + break; + default: + QUICHE_BUG(set_ip_header_included_invalid_family) + << "Invalid address family: " << static_cast<int>(address_family); + return absl::InvalidArgumentError("Invalid address family."); + } + + int value = static_cast<int>(ip_header_included); + int result = ::setsockopt(fd, level, option, &value, sizeof(value)); + + if (result >= 0) { + return absl::OkStatus(); + } else { + absl::Status status = LastSocketOperationError("::setsockopt()"); + QUICHE_DVLOG(1) << "Failed to set socket " << fd << " option " << option + << " to " << value << " with error: " << status; + return status; + } +} + absl::StatusOr<SocketFd> CreateSocket(IpAddressFamily address_family, SocketProtocol protocol, bool blocking) { int flags = 0;
diff --git a/quiche/quic/core/io/socket_test.cc b/quiche/quic/core/io/socket_test.cc index 4bfc27a..58d8136 100644 --- a/quiche/quic/core/io/socket_test.cc +++ b/quiche/quic/core/io/socket_test.cc
@@ -11,6 +11,7 @@ #include "absl/strings/string_view.h" #include "absl/types/span.h" #include "quiche/quic/platform/api/quic_ip_address.h" +#include "quiche/quic/platform/api/quic_ip_address_family.h" #include "quiche/quic/platform/api/quic_socket_address.h" #include "quiche/quic/test_tools/test_ip_packets.h" #include "quiche/common/platform/api/quiche_logging.h" @@ -39,16 +40,35 @@ } } -SocketFd CreateTestRawSocket(bool blocking = true) { - absl::StatusOr<SocketFd> socket = - socket_api::CreateSocket(quiche::TestLoopback().address_family(), - socket_api::SocketProtocol::kRawIp, blocking); +SocketFd CreateTestRawSocket( + bool blocking = true, + IpAddressFamily address_family = IpAddressFamily::IP_UNSPEC) { + absl::StatusOr<SocketFd> socket; + switch (address_family) { + case IpAddressFamily::IP_V4: + socket = socket_api::CreateSocket( + quiche::TestLoopback4().address_family(), + socket_api::SocketProtocol::kRawIp, blocking); + break; + case IpAddressFamily::IP_V6: + socket = socket_api::CreateSocket( + quiche::TestLoopback6().address_family(), + socket_api::SocketProtocol::kRawIp, blocking); + break; + case IpAddressFamily::IP_UNSPEC: + socket = socket_api::CreateSocket(quiche::TestLoopback().address_family(), + socket_api::SocketProtocol::kRawIp, + blocking); + break; + } if (socket.ok()) { return socket.value(); } else { - // This is expected if test not run with relevant admin privileges. - QUICHE_CHECK(absl::IsPermissionDenied(socket.status())); + // This is expected if test not run with relevant admin privileges or if + // address family is unsupported. + QUICHE_CHECK(absl::IsPermissionDenied(socket.status()) || + absl::IsNotFound(socket.status())); return kInvalidSocketFd; } } @@ -107,13 +127,27 @@ } TEST(SocketTest, SetIpHeaderIncludedForRaw) { - SocketFd socket = CreateTestRawSocket(/*blocking=*/true); + SocketFd socket = + CreateTestRawSocket(/*blocking=*/true, IpAddressFamily::IP_V4); if (socket == kInvalidSocketFd) { GTEST_SKIP(); } - QUICHE_EXPECT_OK( - socket_api::SetIpHeaderIncluded(socket, /*ip_header_included=*/true)); + QUICHE_EXPECT_OK(socket_api::SetIpHeaderIncluded( + socket, IpAddressFamily::IP_V4, /*ip_header_included=*/true)); + + QUICHE_EXPECT_OK(socket_api::Close(socket)); +} + +TEST(SocketTest, SetIpHeaderIncludedForRawV6) { + SocketFd socket = + CreateTestRawSocket(/*blocking=*/true, IpAddressFamily::IP_V6); + if (socket == kInvalidSocketFd) { + GTEST_SKIP(); + } + + QUICHE_EXPECT_OK(socket_api::SetIpHeaderIncluded( + socket, IpAddressFamily::IP_V6, /*ip_header_included=*/true)); QUICHE_EXPECT_OK(socket_api::Close(socket)); } @@ -123,9 +157,12 @@ /*blocking=*/true); // Expect option only allowed for raw IP sockets. - EXPECT_THAT( - socket_api::SetIpHeaderIncluded(socket, /*ip_header_included=*/true), - StatusIs(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(socket_api::SetIpHeaderIncluded(socket, IpAddressFamily::IP_V4, + /*ip_header_included=*/true), + StatusIs(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(socket_api::SetIpHeaderIncluded(socket, IpAddressFamily::IP_V6, + /*ip_header_included=*/true), + StatusIs(absl::StatusCode::kInvalidArgument)); QUICHE_EXPECT_OK(socket_api::Close(socket)); } @@ -288,16 +325,17 @@ GTEST_SKIP(); } - QUICHE_EXPECT_OK( - socket_api::SetIpHeaderIncluded(socket, /*ip_header_included=*/false)); + QuicIpAddress localhost_address = quiche::TestLoopback(); + QUICHE_EXPECT_OK(socket_api::SetIpHeaderIncluded( + socket, localhost_address.address_family(), + /*ip_header_included=*/false)); // Arbitrarily-chosen ephemeral ports. - QuicSocketAddress client_address(quiche::TestLoopback(), /*port=*/53368); - QuicSocketAddress server_address(quiche::TestLoopback(), /*port=*/56362); + QuicSocketAddress client_address(localhost_address, /*port=*/53368); + QuicSocketAddress server_address(localhost_address, /*port=*/56362); std::string packet = CreateUdpPacket(client_address, server_address, "foo"); absl::StatusOr<absl::string_view> result = socket_api::SendTo( - socket, QuicSocketAddress(quiche::TestLoopback(), /*port=*/56362), - packet); + socket, QuicSocketAddress(localhost_address, /*port=*/56362), packet); // Expect at least some data to be sent successfully. QUICHE_ASSERT_OK(result.status()); @@ -312,18 +350,18 @@ GTEST_SKIP(); } - QUICHE_EXPECT_OK( - socket_api::SetIpHeaderIncluded(socket, /*ip_header_included=*/true)); + QuicIpAddress localhost_address = quiche::TestLoopback(); + QUICHE_EXPECT_OK(socket_api::SetIpHeaderIncluded( + socket, localhost_address.address_family(), /*ip_header_included=*/true)); // Arbitrarily-chosen ephemeral ports. - QuicSocketAddress client_address(quiche::TestLoopback(), /*port=*/53368); - QuicSocketAddress server_address(quiche::TestLoopback(), /*port=*/56362); + QuicSocketAddress client_address(localhost_address, /*port=*/53368); + QuicSocketAddress server_address(localhost_address, /*port=*/56362); std::string packet = CreateIpPacket(client_address.host(), server_address.host(), CreateUdpPacket(client_address, server_address, "foo")); absl::StatusOr<absl::string_view> result = socket_api::SendTo( - socket, QuicSocketAddress(quiche::TestLoopback(), /*port=*/56362), - packet); + socket, QuicSocketAddress(localhost_address, /*port=*/56362), packet); // Expect at least some data to be sent successfully. QUICHE_ASSERT_OK(result.status());
diff --git a/quiche/quic/core/io/socket_win.inc b/quiche/quic/core/io/socket_win.inc index c885722..d6ba18c 100644 --- a/quiche/quic/core/io/socket_win.inc +++ b/quiche/quic/core/io/socket_win.inc
@@ -10,6 +10,9 @@ #include "absl/strings/string_view.h" #include "quiche/quic/core/io/socket.h" #include "quiche/quic/core/io/socket_internal.h" +#include "quiche/quic/platform/api/quic_ip_address_family.h" +#include "quiche/common/platform/api/quiche_bug_tracker.h" +#include "quiche/common/platform/api/quiche_logging.h" #include "quiche/common/quiche_status_utils.h" namespace quic::socket_api { @@ -163,6 +166,41 @@ return StatusForLastCall(result, "SetSocketBlocking()"); } +absl::Status SetIpHeaderIncluded(SocketFd fd, IpAddressFamily address_family, + bool ip_header_included) { + QUICHE_DCHECK_NE(fd, kInvalidSocketFd); + + int level; + int option; + switch (address_family) { + case IpAddressFamily::IP_V4: + level = IPPROTO_IP; + option = IP_HDRINCL; + break; + case IpAddressFamily::IP_V6: + level = IPPROTO_IPV6; + option = IPV6_HDRINCL; + break; + default: + QUICHE_BUG(set_ip_header_included_invalid_family) + << "Invalid address family: " << static_cast<int>(address_family); + return absl::InvalidArgumentError("Invalid address family."); + } + + int value = static_cast<int>(ip_header_included); + int result = ::setsockopt( + fd, level, option, reinterpret_cast<const char*>(&value), sizeof(value)); + + if (result >= 0) { + return absl::OkStatus(); + } else { + absl::Status status = StatusForLastCall(result, "::setsockopt()"); + QUICHE_DVLOG(1) << "Failed to set socket " << fd << " option " << option + << " to " << value << " with error: " << status; + return status; + } +} + absl::Status Close(SocketFd fd) { int result = ::closesocket(fd); return StatusForLastCall(result, "close()");