Add support for setting SO_MARK on ICMP sockets.

When set it will be used to manually steer the socket traffic to a specific device queue at the kernel routing.

PiperOrigin-RevId: 975276589
diff --git a/quiche/quic/qbone/bonnet/icmp_reachable.cc b/quiche/quic/qbone/bonnet/icmp_reachable.cc
index 7bee28c..42f1d1f 100644
--- a/quiche/quic/qbone/bonnet/icmp_reachable.cc
+++ b/quiche/quic/qbone/bonnet/icmp_reachable.cc
@@ -5,14 +5,21 @@
 #include "quiche/quic/qbone/bonnet/icmp_reachable.h"
 
 #include <netinet/ip6.h>
+#include <sys/socket.h>
 
+#include <cerrno>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
 #include <string>
 
 #include "absl/strings/string_view.h"
-#include "quiche/quic/core/crypto/quic_random.h"
+#include "absl/synchronization/mutex.h"
 #include "quiche/quic/core/io/quic_event_loop.h"
+#include "quiche/quic/core/quic_time.h"
+#include "quiche/quic/platform/api/quic_ip_address.h"
 #include "quiche/quic/platform/api/quic_logging.h"
-#include "quiche/quic/qbone/platform/icmp_packet.h"
+#include "quiche/quic/qbone/platform/kernel_interface.h"
 #include "quiche/common/platform/api/quiche_logging.h"
 #include "quiche/common/quiche_text_utils.h"
 
@@ -33,7 +40,8 @@
 IcmpReachable::IcmpReachable(absl::string_view interface_name,
                              QuicIpAddress source, QuicIpAddress destination,
                              QuicTime::Delta timeout, KernelInterface* kernel,
-                             QuicEventLoop* event_loop, StatsInterface* stats)
+                             QuicEventLoop* event_loop, StatsInterface* stats,
+                             uint32_t socket_mark)
     : timeout_(timeout),
       event_loop_(event_loop),
       clock_(event_loop->GetClock()),
@@ -42,7 +50,8 @@
       alarm_(alarm_factory_->CreateAlarm(new AlarmCallback(this))),
       kernel_(kernel),
       stats_(stats),
-      sock_fd_(0) {
+      sock_fd_(0),
+      socket_mark_(socket_mark) {
   src_.sin6_family = AF_INET6;
   dst_.sin6_family = AF_INET6;
   // Ensure the destination has its scope set to the QBONE TUN/TAP device.
@@ -71,6 +80,14 @@
     return false;
   }
 
+  if (socket_mark_ > 0) {
+    if (kernel_->setsockopt(sock_fd_, SOL_SOCKET, SO_MARK, &socket_mark_,
+                            sizeof(socket_mark_)) < 0) {
+      QUIC_PLOG(ERROR) << "Unable to set SO_MARK on ICMP socket.";
+      return false;
+    }
+  }
+
   if (kernel_->bind(sock_fd_, reinterpret_cast<struct sockaddr*>(&src_),
                     sizeof(sockaddr_in6)) < 0) {
     QUIC_PLOG(ERROR) << "Unable to bind ICMP socket.";
diff --git a/quiche/quic/qbone/bonnet/icmp_reachable.h b/quiche/quic/qbone/bonnet/icmp_reachable.h
index d88632a..b9aae77 100644
--- a/quiche/quic/qbone/bonnet/icmp_reachable.h
+++ b/quiche/quic/qbone/bonnet/icmp_reachable.h
@@ -7,7 +7,9 @@
 
 #include <netinet/icmp6.h>
 
+#include <cstdint>
 #include <memory>
+#include <string>
 
 #include "absl/base/thread_annotations.h"
 #include "absl/strings/string_view.h"
@@ -73,10 +75,12 @@
   //                Server's thread.
   // |stats| is not owned, but should outlive this instance. It will be called
   //         back on Echo Replies, timeouts, and I/O errors.
+  // |socket_mark| is the SO_MARK value to apply to the ICMP socket (for
+  //               multiqueue queue steering). Defaults to 0 (no mark).
   IcmpReachable(absl::string_view interface_name, QuicIpAddress source,
                 QuicIpAddress destination, QuicTime::Delta timeout,
                 KernelInterface* kernel, QuicEventLoop* event_loop,
-                StatsInterface* stats);
+                StatsInterface* stats, uint32_t socket_mark = 0);
 
   ~IcmpReachable() override;
 
@@ -142,6 +146,8 @@
 
   QuicTime start_ = QuicTime::Zero();
   QuicTime end_ = QuicTime::Zero();
+
+  uint32_t socket_mark_ = 0;
 };
 
 }  // namespace quic
diff --git a/quiche/quic/qbone/bonnet/icmp_reachable_test.cc b/quiche/quic/qbone/bonnet/icmp_reachable_test.cc
index 492d578..b7e8f0f 100644
--- a/quiche/quic/qbone/bonnet/icmp_reachable_test.cc
+++ b/quiche/quic/qbone/bonnet/icmp_reachable_test.cc
@@ -6,16 +6,23 @@
 
 #include <netinet/ip6.h>
 
+#include <cerrno>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
 #include <memory>
 #include <string>
 
 #include "absl/container/node_hash_map.h"
+#include "absl/strings/string_view.h"
 #include "quiche/quic/core/io/quic_default_event_loop.h"
 #include "quiche/quic/core/io/quic_event_loop.h"
 #include "quiche/quic/core/quic_default_clock.h"
+#include "quiche/quic/core/quic_time.h"
 #include "quiche/quic/platform/api/quic_ip_address.h"
 #include "quiche/quic/platform/api/quic_test.h"
 #include "quiche/quic/qbone/platform/mock_kernel.h"
+#include "quiche/common/platform/api/quiche_logging.h"
 
 namespace quic::test {
 namespace {
@@ -266,5 +273,33 @@
   EXPECT_EQ(stats_.ReadErrorCount(EIO), 1);
 }
 
+TEST_F(IcmpReachableTest, SetsSocketMarkWhenConfigured) {
+  uint32_t socket_mark = 1;
+  InSequence seq;
+  EXPECT_CALL(kernel_, if_nametoindex(_));
+  EXPECT_CALL(kernel_, socket(_, _, _)).WillOnce(Return(simulated_sock_fd_));
+  EXPECT_CALL(kernel_, setsockopt(simulated_sock_fd_, SOL_SOCKET, SO_MARK, _,
+                                  sizeof(socket_mark)))
+      .WillOnce([socket_mark](int fd, int level, int optname,
+                              const void* optval, socklen_t optlen) {
+        EXPECT_EQ(*reinterpret_cast<const uint32_t*>(optval), socket_mark);
+        return 0;
+      });
+  EXPECT_CALL(kernel_, bind(simulated_sock_fd_, _, _)).WillOnce(Return(0));
+  EXPECT_CALL(kernel_, getsockname(_, _, _))
+      .WillOnce(DoAll(
+          SetArgPointee<1>(*reinterpret_cast<struct sockaddr*>(&send_socket_)),
+          Return(0)));
+  EXPECT_CALL(kernel_, close(simulated_sock_fd_)).WillOnce([](int fd) {
+    return close(fd);
+  });
+
+  IcmpReachable reachable(kInterfaceName, source_, destination_,
+                          QuicTime::Delta::Zero(), &kernel_, event_loop_.get(),
+                          &stats_, socket_mark);
+
+  ASSERT_TRUE(reachable.Init());
+}
+
 }  // namespace
 }  // namespace quic::test