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_