IETF CONNECT-IP Hackathon: TUN device for masque_client

This change makes it possible for masque_client to bring up a TUN
device, rather than sending a single encapsulated request, if
--bring_up_tun is passed.

For now, the MTU is hard-coded to 1280, but with this, a connection to
testvm.masque.uno is able to bring up a tun0 that can hit https://www.google.com!

```
blaze build third_party/quic/masque/... && cp -f blaze-bin/third_party/quic/masque/masque_client /tmp && sudo /tmp/masque_client --disable_certificate_verification --masque_mode=connect-ip testvm.masque.uno:9661 --bring_up_tun --uid= --alsologtostderr
curl -D /dev/stdout -4 --interface tun0 "https://www.google.com"
```

PiperOrigin-RevId: 486375988
diff --git a/quiche/quic/masque/masque_client_bin.cc b/quiche/quic/masque/masque_client_bin.cc
index b0ec7c0..08c8bc0 100644
--- a/quiche/quic/masque/masque_client_bin.cc
+++ b/quiche/quic/masque/masque_client_bin.cc
@@ -40,10 +40,96 @@
     std::string, masque_mode, "",
     "Allows setting MASQUE mode, currently only valid value is \"open\".");
 
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+    bool, bring_up_tun, false,
+    "If set to true, no URLs need to be specified and instead a TUN device "
+    "is brought up with the assigned IP from the MASQUE CONNECT-IP server");
+
 namespace quic {
 
 namespace {
 
+class MasqueTunSession : public MasqueClientSession::EncapsulatedIpSession,
+                         public QuicSocketEventListener {
+ public:
+  MasqueTunSession(QuicEventLoop* event_loop, MasqueClientSession* session)
+      : event_loop_(event_loop), session_(session) {}
+  ~MasqueTunSession() override = default;
+  // MasqueClientSession::EncapsulatedIpSession
+  void ProcessIpPacket(absl::string_view packet) override {
+    QUIC_LOG(INFO) << " Received IP packets of length " << packet.length();
+    if (fd_ == -1) {
+      // TUN not open, early return
+      return;
+    }
+    write(fd_, packet.data(), packet.size());
+  }
+  void CloseIpSession(const std::string& details) override {
+    QUIC_LOG(ERROR) << "Was asked to close IP session: " << details;
+  }
+  bool OnAddressAssignCapsule(const AddressAssignCapsule& capsule) override {
+    for (auto assigned_address : capsule.assigned_addresses) {
+      if (assigned_address.ip_prefix.address().IsIPv4()) {
+        QUIC_LOG(INFO) << "MasqueTunSession saving local IPv4 address "
+                       << assigned_address.ip_prefix.address();
+        local_address_ = assigned_address.ip_prefix.address();
+        break;
+      }
+    }
+    // Bring up the TUN
+    QUIC_LOG(ERROR) << "Bringing up tun with address " << local_address_;
+    fd_ = CreateTunInterface(local_address_, false);
+    if (fd_ < 0) {
+      QUIC_LOG(FATAL) << "Failed to create TUN interface";
+    }
+    if (!event_loop_->RegisterSocket(fd_, kSocketEventReadable, this)) {
+      QUIC_LOG(FATAL) << "Failed to register TUN fd with the event loop";
+    }
+    return true;
+  }
+  bool OnAddressRequestCapsule(
+      const AddressRequestCapsule& /*capsule*/) override {
+    // Always ignore the address request capsule from the server.
+    return true;
+  }
+  bool OnRouteAdvertisementCapsule(
+      const RouteAdvertisementCapsule& /*capsule*/) override {
+    // Consider installing routes.
+    return true;
+  }
+
+  // QuicSocketEventListener
+  void OnSocketEvent(QuicEventLoop* /*event_loop*/, QuicUdpSocketFd fd,
+                     QuicSocketEventMask events) override {
+    if ((events & kSocketEventReadable) == 0) {
+      QUIC_DVLOG(1) << "Ignoring OnEvent fd " << fd << " event mask " << events;
+      return;
+    }
+    char datagram[1501];
+    while (true) {
+      ssize_t read_size = read(fd, datagram, sizeof(datagram));
+      if (read_size < 0) {
+        break;
+      }
+      // Packet received from the TUN. Write it to the MASQUE CONNECT-IP
+      // session.
+      session_->SendIpPacket(absl::string_view(datagram, read_size), this);
+    }
+    if (!event_loop_->SupportsEdgeTriggered()) {
+      if (!event_loop_->RearmSocket(fd, kSocketEventReadable)) {
+        QUIC_BUG(MasqueServerSession_ConnectIp_OnSocketEvent_Rearm)
+            << "Failed to re-arm socket " << fd << " for reading";
+      }
+    }
+  }
+
+ private:
+  QuicEventLoop* event_loop_;
+  MasqueClientSession* session_;
+  QuicIpAddress local_address_;
+  int fd_ = -1;
+};
+
 int RunMasqueClient(int argc, char* argv[]) {
   quiche::QuicheSystemEventLoop system_event_loop("masque_client");
   const char* usage = "Usage: masque_client [options] <url>";
@@ -55,7 +141,8 @@
   // {?target_host,target_port}.
   std::vector<std::string> urls =
       quiche::QuicheParseCommandLineFlags(usage, argc, argv);
-  if (urls.empty()) {
+  bool bring_up_tun = quiche::GetQuicheCommandLineFlag(FLAGS_bring_up_tun);
+  if (urls.empty() && !bring_up_tun) {
     quiche::QuichePrintCommandLineFlagHelp(usage);
     return 1;
   }
@@ -125,6 +212,18 @@
   std::cerr << "MASQUE is connected " << masque_client->connection_id()
             << " in " << masque_mode << " mode" << std::endl;
 
+  if (bring_up_tun) {
+    std::cerr << "Bringing up tun" << std::endl;
+    MasqueTunSession tun_session(event_loop.get(),
+                                 masque_client->masque_client_session());
+    masque_client->masque_client_session()->SendIpPacket(
+        absl::string_view("asdf"), &tun_session);
+    while (true) {
+      event_loop->RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(50));
+    }
+    return 0;
+  }
+
   for (size_t i = 1; i < urls.size(); ++i) {
     if (!tools::SendEncapsulatedMasqueRequest(
             masque_client.get(), event_loop.get(), urls[i],
diff --git a/quiche/quic/masque/masque_server_session.cc b/quiche/quic/masque/masque_server_session.cc
index 515d9ab..f770b70 100644
--- a/quiche/quic/masque/masque_server_session.cc
+++ b/quiche/quic/masque/masque_server_session.cc
@@ -5,13 +5,10 @@
 #include "quiche/quic/masque/masque_server_session.h"
 
 #include <fcntl.h>
-#include <linux/if.h>
-#include <linux/if_tun.h>
 #include <netdb.h>
 #include <netinet/ip.h>
 #include <netinet/ip_icmp.h>
 #include <netinet/udp.h>
-#include <sys/ioctl.h>
 
 #include <cstddef>
 #include <cstdint>
@@ -26,6 +23,7 @@
 #include "quiche/quic/core/io/quic_event_loop.h"
 #include "quiche/quic/core/quic_data_reader.h"
 #include "quiche/quic/core/quic_udp_socket.h"
+#include "quiche/quic/masque/masque_utils.h"
 #include "quiche/quic/platform/api/quic_ip_address.h"
 #include "quiche/quic/tools/quic_url.h"
 #include "quiche/common/platform/api/quiche_url_utils.h"
@@ -85,79 +83,6 @@
   return response;
 }
 
-int CreateTunInterface(const QuicIpAddress& client_address) {
-  if (!client_address.IsIPv4()) {
-    QUIC_LOG(ERROR) << "CreateTunInterface currently only supports IPv4";
-    return -1;
-  }
-  int tun_fd = open("/dev/net/tun", O_RDWR);
-  int ip_fd = -1;
-  do {
-    if (tun_fd < 0) {
-      QUIC_PLOG(ERROR) << "Failed to open clone device";
-      break;
-    }
-    struct ifreq ifr = {};
-    ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
-    // If we want to pick a specific device name, we can set it via
-    // ifr.ifr_name. Otherwise, the kernel will pick the next available tunX
-    // name.
-    int err = ioctl(tun_fd, TUNSETIFF, &ifr);
-    if (err < 0) {
-      QUIC_PLOG(ERROR) << "TUNSETIFF failed";
-      break;
-    }
-    ip_fd = socket(AF_INET, SOCK_DGRAM, 0);
-    if (ip_fd < 0) {
-      QUIC_PLOG(ERROR) << "Failed to open IP configuration socket";
-      break;
-    }
-    struct sockaddr_in addr = {};
-    addr.sin_family = AF_INET;
-    // Local address, unused but needs to be set. We use the same address as the
-    // client address, but with last byte set to 1.
-    addr.sin_addr = client_address.GetIPv4();
-    addr.sin_addr.s_addr &= htonl(0xffffff00);
-    addr.sin_addr.s_addr |= htonl(0x00000001);
-    memcpy(&ifr.ifr_addr, &addr, sizeof(addr));
-    err = ioctl(ip_fd, SIOCSIFADDR, &ifr);
-    if (err < 0) {
-      QUIC_PLOG(ERROR) << "SIOCSIFADDR failed";
-      break;
-    }
-    // Peer address, needs to match source IP address of sent packets.
-    addr.sin_addr = client_address.GetIPv4();
-    memcpy(&ifr.ifr_addr, &addr, sizeof(addr));
-    err = ioctl(ip_fd, SIOCSIFDSTADDR, &ifr);
-    if (err < 0) {
-      QUIC_PLOG(ERROR) << "SIOCSIFDSTADDR failed";
-      break;
-    }
-    err = ioctl(ip_fd, SIOCGIFFLAGS, &ifr);
-    if (err < 0) {
-      QUIC_PLOG(ERROR) << "SIOCGIFFLAGS failed";
-      break;
-    }
-    ifr.ifr_flags |= (IFF_UP | IFF_RUNNING);
-    err = ioctl(ip_fd, SIOCSIFFLAGS, &ifr);
-    if (err < 0) {
-      QUIC_PLOG(ERROR) << "SIOCSIFFLAGS failed";
-      break;
-    }
-    close(ip_fd);
-    QUIC_DLOG(INFO) << "Successfully created TUN interface " << ifr.ifr_name
-                    << " with fd " << tun_fd;
-    return tun_fd;
-  } while (false);
-  if (tun_fd >= 0) {
-    close(tun_fd);
-  }
-  if (ip_fd >= 0) {
-    close(ip_fd);
-  }
-  return -1;
-}
-
 }  // namespace
 
 MasqueServerSession::MasqueServerSession(
diff --git a/quiche/quic/masque/masque_utils.cc b/quiche/quic/masque/masque_utils.cc
index 2bab26b..b432a7a 100644
--- a/quiche/quic/masque/masque_utils.cc
+++ b/quiche/quic/masque/masque_utils.cc
@@ -4,6 +4,11 @@
 
 #include "quiche/quic/masque/masque_utils.h"
 
+#include <fcntl.h>
+#include <linux/if.h>
+#include <linux/if_tun.h>
+#include <sys/ioctl.h>
+
 namespace quic {
 
 ParsedQuicVersionVector MasqueSupportedVersions() {
@@ -43,4 +48,93 @@
   return os;
 }
 
+int CreateTunInterface(const QuicIpAddress& client_address, bool server) {
+  if (!client_address.IsIPv4()) {
+    QUIC_LOG(ERROR) << "CreateTunInterface currently only supports IPv4";
+    return -1;
+  }
+  int tun_fd = open("/dev/net/tun", O_RDWR);
+  int ip_fd = -1;
+  do {
+    if (tun_fd < 0) {
+      QUIC_PLOG(ERROR) << "Failed to open clone device";
+      break;
+    }
+    struct ifreq ifr = {};
+    ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
+    // If we want to pick a specific device name, we can set it via
+    // ifr.ifr_name. Otherwise, the kernel will pick the next available tunX
+    // name.
+    int err = ioctl(tun_fd, TUNSETIFF, &ifr);
+    if (err < 0) {
+      QUIC_PLOG(ERROR) << "TUNSETIFF failed";
+      break;
+    }
+    ip_fd = socket(AF_INET, SOCK_DGRAM, 0);
+    if (ip_fd < 0) {
+      QUIC_PLOG(ERROR) << "Failed to open IP configuration socket";
+      break;
+    }
+    struct sockaddr_in addr = {};
+    addr.sin_family = AF_INET;
+    // Local address, unused but needs to be set. We use the same address as the
+    // client address, but with last byte set to 1.
+    addr.sin_addr = client_address.GetIPv4();
+    if (server) {
+      addr.sin_addr.s_addr &= htonl(0xffffff00);
+      addr.sin_addr.s_addr |= htonl(0x00000001);
+    }
+    memcpy(&ifr.ifr_addr, &addr, sizeof(addr));
+    err = ioctl(ip_fd, SIOCSIFADDR, &ifr);
+    if (err < 0) {
+      QUIC_PLOG(ERROR) << "SIOCSIFADDR failed";
+      break;
+    }
+    // Peer address, needs to match source IP address of sent packets.
+    addr.sin_addr = client_address.GetIPv4();
+    if (!server) {
+      addr.sin_addr.s_addr &= htonl(0xffffff00);
+      addr.sin_addr.s_addr |= htonl(0x00000001);
+    }
+    memcpy(&ifr.ifr_addr, &addr, sizeof(addr));
+    err = ioctl(ip_fd, SIOCSIFDSTADDR, &ifr);
+    if (err < 0) {
+      QUIC_PLOG(ERROR) << "SIOCSIFDSTADDR failed";
+      break;
+    }
+    if (!server) {
+      // Set MTU, to 1280 for now which should always fit (fingers crossed)
+      ifr.ifr_mtu = 1280;
+      err = ioctl(ip_fd, SIOCSIFMTU, &ifr);
+      if (err < 0) {
+        QUIC_PLOG(ERROR) << "SIOCSIFMTU failed";
+        break;
+      }
+    }
+
+    err = ioctl(ip_fd, SIOCGIFFLAGS, &ifr);
+    if (err < 0) {
+      QUIC_PLOG(ERROR) << "SIOCGIFFLAGS failed";
+      break;
+    }
+    ifr.ifr_flags |= (IFF_UP | IFF_RUNNING);
+    err = ioctl(ip_fd, SIOCSIFFLAGS, &ifr);
+    if (err < 0) {
+      QUIC_PLOG(ERROR) << "SIOCSIFFLAGS failed";
+      break;
+    }
+    close(ip_fd);
+    QUIC_DLOG(INFO) << "Successfully created TUN interface " << ifr.ifr_name
+                    << " with fd " << tun_fd;
+    return tun_fd;
+  } while (false);
+  if (tun_fd >= 0) {
+    close(tun_fd);
+  }
+  if (ip_fd >= 0) {
+    close(ip_fd);
+  }
+  return -1;
+}
+
 }  // namespace quic
diff --git a/quiche/quic/masque/masque_utils.h b/quiche/quic/masque/masque_utils.h
index 09dbe63..1743092 100644
--- a/quiche/quic/masque/masque_utils.h
+++ b/quiche/quic/masque/masque_utils.h
@@ -40,6 +40,9 @@
 QUIC_NO_EXPORT std::ostream& operator<<(std::ostream& os,
                                         const MasqueMode& masque_mode);
 
+// Create a TUN interface, with the specified `client_address`. Requires root.
+int CreateTunInterface(const QuicIpAddress& client_address, bool server = true);
+
 }  // namespace quic
 
 #endif  // QUICHE_QUIC_MASQUE_MASQUE_UTILS_H_