Add multiqueue support to TunTapDevice.

This change refactors TunTapDevice to support multiple queue file descriptors instead of a single file descriptor.

PiperOrigin-RevId: 974008389
diff --git a/quiche/quic/qbone/bonnet/mock_tun_device.h b/quiche/quic/qbone/bonnet/mock_tun_device.h
index bc5b462..5e086ee 100644
--- a/quiche/quic/qbone/bonnet/mock_tun_device.h
+++ b/quiche/quic/qbone/bonnet/mock_tun_device.h
@@ -22,6 +22,8 @@
 
   MOCK_METHOD(int, GetReadFileDescriptor, (), (const, override));
   MOCK_METHOD(int, GetWriteFileDescriptor, (), (const, override));
+
+  MOCK_METHOD(int, OpenQueue, (), (override));
 };
 
 }  // namespace quic
diff --git a/quiche/quic/qbone/bonnet/tun_device.cc b/quiche/quic/qbone/bonnet/tun_device.cc
index b989467..64a2c3d 100644
--- a/quiche/quic/qbone/bonnet/tun_device.cc
+++ b/quiche/quic/qbone/bonnet/tun_device.cc
@@ -5,16 +5,16 @@
 #include "quiche/quic/qbone/bonnet/tun_device.h"
 
 #include <fcntl.h>
-#include <linux/filter.h>
 #include <linux/if_tun.h>
 #include <net/if.h>
 #include <sys/ioctl.h>
 #include <sys/socket.h>
 
+#include <cstring>
 #include <ios>
 #include <string>
+#include <vector>
 
-#include "absl/cleanup/cleanup.h"
 #include "absl/flags/flag.h"
 #include "quiche/quic/platform/api/quic_bug_tracker.h"
 #include "quiche/quic/platform/api/quic_logging.h"
@@ -35,7 +35,6 @@
       persist_(persist),
       setup_tun_(setup_tun),
       is_tap_(is_tap),
-      file_descriptor_(kInvalidFd),
       kernel_(*kernel) {}
 
 TunTapDevice::~TunTapDevice() {
@@ -95,6 +94,51 @@
   return NetdeviceIoctl(SIOCSIFFLAGS, reinterpret_cast<void*>(&if_request));
 }
 
+int TunTapDevice::GetReadFileDescriptor() const {
+  if (file_descriptors_.empty()) {
+    return kInvalidFd;
+  }
+  return file_descriptors_[0];
+}
+
+int TunTapDevice::GetWriteFileDescriptor() const {
+  if (file_descriptors_.empty()) {
+    return kInvalidFd;
+  }
+  return file_descriptors_[0];
+}
+
+int TunTapDevice::OpenQueue() {
+  struct ifreq if_request;
+  memset(&if_request, 0, sizeof(if_request));
+  interface_name_.copy(if_request.ifr_name, IFNAMSIZ);
+
+  if_request.ifr_flags = IFF_MULTI_QUEUE | IFF_NO_PI;
+  if (is_tap_) {
+    if_request.ifr_flags |= IFF_TAP;
+  } else {
+    if_request.ifr_flags |= IFF_TUN;
+  }
+
+  int new_fd = kInvalidFd;
+  if (!OpenFileDescriptor(kernel_,
+                          absl::GetFlag(FLAGS_qbone_client_tun_device_path),
+                          if_request, O_NONBLOCK | O_RDWR, persist_, &new_fd)) {
+    if (new_fd != kInvalidFd) {
+      kernel_.close(new_fd);
+    }
+    return kInvalidFd;
+  }
+
+  if (new_fd == kInvalidFd) {
+    QUIC_LOG(ERROR) << "OpenFileDescriptor succeeded with an invalid FD.";
+    return kInvalidFd;
+  }
+
+  file_descriptors_.push_back(new_fd);
+  return new_fd;
+}
+
 bool TunTapDevice::CheckFeatures(KernelInterface& kernel, int tun_device_fd) {
   unsigned int actual_features;
   if (kernel.ioctl(tun_device_fd, TUNGETFEATURES, &actual_features) != 0) {
@@ -141,47 +185,8 @@
 }
 
 bool TunTapDevice::OpenDevice() {
-  if (file_descriptor_ != kInvalidFd) {
-    CloseDevice();
-  }
-
-  struct ifreq if_request;
-  memset(&if_request, 0, sizeof(if_request));
-  // copy does not zero-terminate the result string, but we've memset the entire
-  // struct.
-  interface_name_.copy(if_request.ifr_name, IFNAMSIZ);
-
-  // Always set IFF_MULTI_QUEUE since a persistent device does not allow this
-  // flag to be flipped when re-opening it. The only way to flip this flag is to
-  // destroy the device and create a new one, but that deletes any existing
-  // routing associated with the interface, which makes the meaning of the
-  // 'persist' bit ambiguous.
-  if_request.ifr_flags = IFF_MULTI_QUEUE | IFF_NO_PI;
-  if (is_tap_) {
-    if_request.ifr_flags |= IFF_TAP;
-  } else {
-    if_request.ifr_flags |= IFF_TUN;
-  }
-
-  // When the device is running with IFF_MULTI_QUEUE set, each call to open will
-  // create a queue which can be used to read/write packets from/to the device.
-  bool successfully_opened = false;
-  auto cleanup = absl::MakeCleanup([this, &successfully_opened]() {
-    if (!successfully_opened) {
-      CloseDevice();
-    }
-  });
-
-  // We set O_NONBLOCK for good measure, but, from observation, all write()
-  // syscalls to the device seem to be synchronous regardless.
-  if (!OpenFileDescriptor(
-          kernel_, absl::GetFlag(FLAGS_qbone_client_tun_device_path),
-          if_request, O_NONBLOCK | O_RDWR, persist_, &file_descriptor_)) {
-    return false;
-  }
-
-  successfully_opened = true;
-  return successfully_opened;
+  CloseDevice();
+  return OpenQueue() != kInvalidFd;
 }
 
 // TODO(pengg): might be better to use netlink socket, once we have a library to
@@ -223,10 +228,10 @@
 }
 
 void TunTapDevice::CloseDevice() {
-  if (file_descriptor_ != kInvalidFd) {
-    kernel_.close(file_descriptor_);
-    file_descriptor_ = kInvalidFd;
+  for (int fd : file_descriptors_) {
+    kernel_.close(fd);
   }
+  file_descriptors_.clear();
 }
 
 }  // namespace quic
diff --git a/quiche/quic/qbone/bonnet/tun_device.h b/quiche/quic/qbone/bonnet/tun_device.h
index 8eb1615..20ee320 100644
--- a/quiche/quic/qbone/bonnet/tun_device.h
+++ b/quiche/quic/qbone/bonnet/tun_device.h
@@ -5,6 +5,9 @@
 #ifndef QUICHE_QUIC_QBONE_BONNET_TUN_DEVICE_H_
 #define QUICHE_QUIC_QBONE_BONNET_TUN_DEVICE_H_
 
+#include <net/if.h>
+
+#include <cstddef>
 #include <string>
 #include <vector>
 
@@ -46,15 +49,20 @@
   // Marks the interface down to stop receiving packets.
   bool Down() override;
 
-  // Closes the open file descriptor for the TUN device (if one exists).
+  // Closes all open queue file descriptors for the TUN device.
   // It is safe to reinitialize and reuse this TunTapDevice after calling
   // CloseDevice.
   void CloseDevice() override;
 
-  // Get the file descriptors that can be used to send/receive packets.
+  // Get the file descriptors that can be used to send/receive packets for
+  // the primary queue (queue 0).
   // These return -1 when the TUN device is in an invalid state.
-  int GetReadFileDescriptor() const override { return file_descriptor_; }
-  int GetWriteFileDescriptor() const override { return file_descriptor_; }
+  int GetReadFileDescriptor() const override;
+  int GetWriteFileDescriptor() const override;
+
+  // Opens the next sequential queue file descriptor for this multiqueue device.
+  // Returns the open queue FD on success, or -1 on failure.
+  int OpenQueue() override;
 
   static bool CheckFeatures(KernelInterface& kernel, int tun_device_fd);
   static bool OpenFileDescriptor(KernelInterface& kernel,
@@ -62,7 +70,8 @@
                                  int flags, bool persist, int* return_fd);
 
  private:
-  // Creates or reopens the tun device.
+  // Resets the device by closing all open queues and opening a fresh primary
+  // queue (queue 0).
   bool OpenDevice();
 
   // Configure the interface.
@@ -76,8 +85,9 @@
   const bool persist_;
   const bool setup_tun_;
   const bool is_tap_;
-  int file_descriptor_;
   KernelInterface& kernel_;
+
+  std::vector<int> file_descriptors_;
 };
 
 }  // namespace quic
diff --git a/quiche/quic/qbone/bonnet/tun_device_interface.h b/quiche/quic/qbone/bonnet/tun_device_interface.h
index 1bec7e9..57444c6 100644
--- a/quiche/quic/qbone/bonnet/tun_device_interface.h
+++ b/quiche/quic/qbone/bonnet/tun_device_interface.h
@@ -5,8 +5,6 @@
 #ifndef QUICHE_QUIC_QBONE_BONNET_TUN_DEVICE_INTERFACE_H_
 #define QUICHE_QUIC_QBONE_BONNET_TUN_DEVICE_INTERFACE_H_
 
-#include <vector>
-
 namespace quic {
 
 // An interface with methods for interacting with a TUN device.
@@ -32,6 +30,10 @@
   // These return -1 when the TUN device is in an invalid state.
   virtual int GetReadFileDescriptor() const = 0;
   virtual int GetWriteFileDescriptor() const = 0;
+
+  // Opens the next sequential queue file descriptor for this multiqueue device.
+  // Returns the open queue FD on success, or -1 on failure.
+  virtual int OpenQueue() = 0;
 };
 
 }  // namespace quic
diff --git a/quiche/quic/qbone/bonnet/tun_device_test.cc b/quiche/quic/qbone/bonnet/tun_device_test.cc
index 1e3b9de..ccbab2d 100644
--- a/quiche/quic/qbone/bonnet/tun_device_test.cc
+++ b/quiche/quic/qbone/bonnet/tun_device_test.cc
@@ -41,7 +41,7 @@
   }
 
   // Set the expectations for calling Init().
-  void SetInitExpectations(int mtu, bool persist) {
+  void SetInitExpectations(int mtu, bool persist, bool is_tap = false) {
     EXPECT_CALL(mock_kernel_, open(StrEq("/dev/net/tun"), _))
         .Times(AnyNumber())
         .WillRepeatedly([this](Unused, Unused) {
@@ -57,9 +57,11 @@
         });
     EXPECT_CALL(mock_kernel_, ioctl(_, TUNSETIFF, _))
         .Times(AnyNumber())
-        .WillRepeatedly([](Unused, Unused, void* argp) {
+        .WillRepeatedly([is_tap](Unused, Unused, void* argp) {
           auto* ifr = reinterpret_cast<struct ifreq*>(argp);
-          EXPECT_EQ(IFF_TUN | IFF_MULTI_QUEUE | IFF_NO_PI, ifr->ifr_flags);
+          int expected_flags =
+              (is_tap ? IFF_TAP : IFF_TUN) | IFF_MULTI_QUEUE | IFF_NO_PI;
+          EXPECT_EQ(expected_flags, ifr->ifr_flags);
           EXPECT_THAT(ifr->ifr_name, StrEq(kDeviceName));
           return 0;
         });
@@ -215,5 +217,81 @@
   EXPECT_FALSE(tun_device.Up());
 }
 
+TEST_F(TunDeviceTest, OpensMultipleQueuesDynamically) {
+  SetInitExpectations(/* mtu = */ 1500, /* persist = */ false);
+  TunTapDevice tun_device(kDeviceName, 1500, false, true, false, &mock_kernel_);
+  ASSERT_TRUE(tun_device.Init());
+
+  int fd_0 = tun_device.GetReadFileDescriptor();
+  EXPECT_GT(fd_0, -1);
+  EXPECT_EQ(tun_device.GetWriteFileDescriptor(), fd_0);
+
+  int fd_1 = tun_device.OpenQueue();
+  EXPECT_GT(fd_1, -1);
+  EXPECT_NE(fd_0, fd_1);
+
+  int fd_2 = tun_device.OpenQueue();
+  EXPECT_GT(fd_2, -1);
+  EXPECT_NE(fd_1, fd_2);
+
+  tun_device.CloseDevice();
+  EXPECT_EQ(tun_device.GetReadFileDescriptor(), -1);
+  EXPECT_EQ(tun_device.GetWriteFileDescriptor(), -1);
+
+  // Calling CloseDevice() again is idempotent.
+  tun_device.CloseDevice();
+  EXPECT_EQ(tun_device.GetReadFileDescriptor(), -1);
+  EXPECT_EQ(tun_device.GetWriteFileDescriptor(), -1);
+
+  ExpectDown(/* fail = */ false);
+}
+
+TEST_F(TunDeviceTest, SecondaryQueueOpenFailsCleanly) {
+  SetInitExpectations(/* mtu = */ 1500, /* persist = */ false);
+  TunTapDevice tun_device(kDeviceName, 1500, false, true, false, &mock_kernel_);
+  ASSERT_TRUE(tun_device.Init());
+
+  // Expect opening queue 1 to fail during TUNSETIFF.
+  EXPECT_CALL(mock_kernel_, ioctl(_, TUNSETIFF, _)).WillOnce(Return(-1));
+
+  EXPECT_EQ(tun_device.OpenQueue(), -1);
+  EXPECT_GT(tun_device.GetReadFileDescriptor(), -1);
+
+  ExpectDown(/* fail = */ false);
+}
+
+TEST_F(TunDeviceTest, CallingInitWhileQueuesAreOpenResetsToPrimaryQueue) {
+  SetInitExpectations(/* mtu = */ 1500, /* persist = */ false);
+  TunTapDevice tun_device(kDeviceName, 1500, false, true, false, &mock_kernel_);
+  ASSERT_TRUE(tun_device.Init());
+  int fd_0_orig = tun_device.GetReadFileDescriptor();
+  int fd_1_orig = tun_device.OpenQueue();
+  int fd_2_orig = tun_device.OpenQueue();
+  ASSERT_GT(fd_1_orig, -1);
+  ASSERT_GT(fd_2_orig, -1);
+
+  // Calling Init() again must close all existing queues and reset the device
+  // to only have a single primary queue.
+  ASSERT_TRUE(tun_device.Init());
+  int fd_0_new = tun_device.GetReadFileDescriptor();
+  EXPECT_GT(fd_0_new, -1);
+  EXPECT_NE(fd_0_new, fd_0_orig);
+
+  ExpectDown(/* fail = */ false);
+}
+
+TEST_F(TunDeviceTest, TapDeviceSetsTapFlag) {
+  SetInitExpectations(/* mtu = */ 1500, /* persist = */ false,
+                      /* is_tap = */ true);
+  TunTapDevice tap_device(kDeviceName, 1500, false, true, /* is_tap = */ true,
+                          &mock_kernel_);
+  EXPECT_TRUE(tap_device.Init());
+
+  int fd_1 = tap_device.OpenQueue();
+  EXPECT_GT(fd_1, -1);
+
+  ExpectDown(/* fail = */ false);
+}
+
 }  // namespace
 }  // namespace quic::test