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()");