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