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_