Add tun_device_integration_test

Mostly just a slightly cleaned up version of the code I've been using to manually experiment with TUN, just for the sake of getting it submitted.

PiperOrigin-RevId: 888216587
diff --git a/build/source_list.bzl b/build/source_list.bzl
index 434977b..f6286ad 100644
--- a/build/source_list.bzl
+++ b/build/source_list.bzl
@@ -1755,6 +1755,7 @@
     "quic/qbone/bonnet/tun_device.cc",
     "quic/qbone/bonnet/tun_device_controller.cc",
     "quic/qbone/bonnet/tun_device_controller_test.cc",
+    "quic/qbone/bonnet/tun_device_integration_test.cc",
     "quic/qbone/bonnet/tun_device_packet_exchanger.cc",
     "quic/qbone/bonnet/tun_device_packet_exchanger_test.cc",
     "quic/qbone/bonnet/tun_device_test.cc",
diff --git a/build/source_list.gni b/build/source_list.gni
index 9b85f15..f112266 100644
--- a/build/source_list.gni
+++ b/build/source_list.gni
@@ -1760,6 +1760,7 @@
     "src/quiche/quic/qbone/bonnet/tun_device.cc",
     "src/quiche/quic/qbone/bonnet/tun_device_controller.cc",
     "src/quiche/quic/qbone/bonnet/tun_device_controller_test.cc",
+    "src/quiche/quic/qbone/bonnet/tun_device_integration_test.cc",
     "src/quiche/quic/qbone/bonnet/tun_device_packet_exchanger.cc",
     "src/quiche/quic/qbone/bonnet/tun_device_packet_exchanger_test.cc",
     "src/quiche/quic/qbone/bonnet/tun_device_test.cc",
diff --git a/build/source_list.json b/build/source_list.json
index c30464e..06bb7e5 100644
--- a/build/source_list.json
+++ b/build/source_list.json
@@ -1759,6 +1759,7 @@
     "quiche/quic/qbone/bonnet/tun_device.cc",
     "quiche/quic/qbone/bonnet/tun_device_controller.cc",
     "quiche/quic/qbone/bonnet/tun_device_controller_test.cc",
+    "quiche/quic/qbone/bonnet/tun_device_integration_test.cc",
     "quiche/quic/qbone/bonnet/tun_device_packet_exchanger.cc",
     "quiche/quic/qbone/bonnet/tun_device_packet_exchanger_test.cc",
     "quiche/quic/qbone/bonnet/tun_device_test.cc",
diff --git a/quiche/quic/qbone/bonnet/tun_device_integration_test.cc b/quiche/quic/qbone/bonnet/tun_device_integration_test.cc
new file mode 100644
index 0000000..c5c50d0
--- /dev/null
+++ b/quiche/quic/qbone/bonnet/tun_device_integration_test.cc
@@ -0,0 +1,148 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <linux/if_tun.h>
+
+#include <cerrno>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/str_format.h"
+#include "absl/types/span.h"
+#include "quiche/quic/core/crypto/quic_random.h"
+#include "quiche/quic/core/io/socket.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/platform/api/quic_test.h"
+#include "quiche/quic/qbone/bonnet/tun_device.h"
+#include "quiche/quic/qbone/bonnet/tun_device_controller.h"
+#include "quiche/quic/qbone/platform/ip_range.h"
+#include "quiche/quic/qbone/platform/kernel_interface.h"
+#include "quiche/quic/qbone/platform/netlink.h"
+#include "quiche/quic/test_tools/test_ip_packets.h"
+
+namespace quic::test {
+namespace {
+
+// Tests for TunDevice that rely on the real kernel and bring up a real tun
+// device. Mostly functions as an experimentation playground for poking at TUN.
+class TunDeviceIntegrationTest : public QuicTest {
+ protected:
+  void SetUp() override {
+    ASSERT_TRUE(local_address_.FromString("2001:db8:2026:1::"));
+    ASSERT_TRUE(remote_address_.FromString("2001:db8:2026:2::"));
+
+    std::string interface_name = absl::StrFormat(
+        "qbone-test-%d",
+        QuicRandom::GetInstance()->InsecureRandUint64() % 10000);
+    tun_device_ = std::make_unique<TunTapDevice>(
+        interface_name, /*mtu=*/1600, /*persist=*/false, /*setup_tun=*/true,
+        /*is_tap=*/false, &kernel_);
+    tun_device_controller_ = std::make_unique<TunDeviceController>(
+        interface_name, /*setup_tun=*/true, &netlink_);
+  }
+
+  QuicIpAddress local_address_;
+  QuicIpAddress remote_address_;
+
+  Kernel kernel_;
+  Netlink netlink_{&kernel_};
+  std::unique_ptr<TunTapDevice> tun_device_;
+  std::unique_ptr<TunDeviceController> tun_device_controller_;
+};
+
+absl::Status SetNonBlocking(SocketFd fd) {
+  int flags = ::fcntl(fd, F_GETFL, 0);
+  if (flags < 0) {
+    return absl::ErrnoToStatus(errno, "Failed to get flags");
+  }
+  if (::fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
+    return absl::ErrnoToStatus(errno, "Failed to set flags");
+  }
+  return absl::OkStatus();
+}
+
+TEST_F(TunDeviceIntegrationTest, MassiveNumWrites) {
+  ASSERT_TRUE(tun_device_->Init());
+  ASSERT_GT(tun_device_->GetWriteFileDescriptor(), -1);
+  ASSERT_TRUE(tun_device_controller_->UpdateAddress(
+      {IpRange(local_address_, /*prefix_length=*/64)}));
+  ASSERT_TRUE(tun_device_->Up());
+
+  int sndbuf = 500;
+  ASSERT_GE(kernel_.ioctl(tun_device_->GetWriteFileDescriptor(), TUNSETSNDBUF,
+                          &sndbuf),
+            0);
+
+  ASSERT_OK(SetNonBlocking(tun_device_->GetWriteFileDescriptor()));
+
+  QuicSocketAddress source_endpoint(remote_address_, /*port=*/53368);
+  QuicSocketAddress destination_endpoint(local_address_, /*port=*/56362);
+  std::string payload(256, 'a');
+  std::string packet = CreateIpPacket(
+      source_endpoint.host(), destination_endpoint.host(),
+      CreateUdpPacket(source_endpoint, destination_endpoint, payload));
+
+  absl::StatusOr<SocketFd> udp_socket = socket_api::CreateSocket(
+      IpAddressFamily::IP_V6, socket_api::SocketProtocol::kUdp,
+      /*blocking=*/false);
+  ASSERT_OK(udp_socket);
+  OwnedSocketFd owned_udp_socket(udp_socket.value());
+
+  ASSERT_OK(socket_api::Bind(udp_socket.value(), destination_endpoint));
+
+  std::vector<char> receive_buffer(1600);
+  for (int i = 0; i < 1000000; ++i) {
+    ASSERT_EQ(kernel_.write(tun_device_->GetWriteFileDescriptor(),
+                            packet.data(), packet.size()),
+              packet.size())
+        << "Write failed on iteration " << i << " with error " << errno;
+
+    absl::StatusOr<absl::Span<char>> receive_data =
+        socket_api::Receive(udp_socket.value(), absl::MakeSpan(receive_buffer));
+    ASSERT_OK(receive_data)
+        << "Receive failed on iteration " << i << " with error "
+        << receive_data.status().message();
+  }
+}
+
+TEST_F(TunDeviceIntegrationTest, MassiveWrite) {
+  ASSERT_TRUE(tun_device_->Init());
+  ASSERT_GT(tun_device_->GetWriteFileDescriptor(), -1);
+  ASSERT_TRUE(tun_device_controller_->UpdateAddress(
+      {IpRange(local_address_, /*prefix_length=*/64)}));
+  ASSERT_TRUE(tun_device_->Up());
+
+  QuicSocketAddress source_endpoint(remote_address_, /*port=*/53368);
+  QuicSocketAddress destination_endpoint(local_address_, /*port=*/56362);
+  std::string payload(65527, 'a');
+  std::string packet = CreateIpPacket(
+      source_endpoint.host(), destination_endpoint.host(),
+      CreateUdpPacket(source_endpoint, destination_endpoint, payload));
+
+  absl::StatusOr<SocketFd> udp_socket = socket_api::CreateSocket(
+      IpAddressFamily::IP_V6, socket_api::SocketProtocol::kUdp,
+      /*blocking=*/false);
+  ASSERT_OK(udp_socket);
+  OwnedSocketFd owned_udp_socket(udp_socket.value());
+
+  ASSERT_OK(socket_api::Bind(udp_socket.value(), destination_endpoint));
+
+  ASSERT_EQ(kernel_.write(tun_device_->GetWriteFileDescriptor(), packet.data(),
+                          packet.size()),
+            packet.size());
+
+  std::vector<char> receive_buffer(payload.size() + 1000);
+  absl::StatusOr<absl::Span<char>> receive_data =
+      socket_api::Receive(udp_socket.value(), absl::MakeSpan(receive_buffer));
+  ASSERT_OK(receive_data);
+  ASSERT_EQ(receive_data->size(), payload.size());
+}
+
+}  // namespace
+}  // namespace quic::test