CONNECT-IP server support This CL adds server-side support for draft-ietf-masque-connect-ip-03. It also fixes a few bugs found in client code now that we can test it. This was confirmed to work on the Privacy Proxy test VM (cf go/privacy-proxy-vm). To test, run these commands: # On CloudTop / workstation blaze build //third_party/quic/masque:masque_server && gsutil cp blaze-bin/third_party/quic/masque/masque_server gs://pp-toybox-server # On VM gsutil cp gs://pp-toybox-server/masque_server . && chmod +x masque_server && ./masque_server --gid= --uid= --logtostderr --v=1 # On CloudTop / workstation blaze run //third_party/quic/masque:masque_client -- --alsologtostderr --v=1 --disable_certificate_verification --masque_mode=connect-ip testvm.masque.uno:9661 https://test.privateoctopus.com:4433/ PiperOrigin-RevId: 485589201
diff --git a/quiche/quic/masque/masque_client_session.cc b/quiche/quic/masque/masque_client_session.cc index 8e5bbfa..e9d6d8b 100644 --- a/quiche/quic/masque/masque_client_session.cc +++ b/quiche/quic/masque/masque_client_session.cc
@@ -39,9 +39,7 @@ crypto_config, push_promise_index), masque_mode_(masque_mode), uri_template_(uri_template), - owner_(owner) { - connection->SetMaxPacketLength(1400); -} + owner_(owner) {} void MasqueClientSession::OnMessageAcked(QuicMessageId message_id, QuicTime /*receive_timestamp*/) { @@ -218,8 +216,9 @@ MessageStatus message_status = SendHttp3Datagram(connect_ip->stream()->id(), http_payload); - QUIC_DVLOG(1) << "Sent IP packet with stream ID " - << connect_ip->stream()->id() << " and got message status " + QUIC_DVLOG(1) << "Sent encapsulated IP packet of length " << packet.size() + << " with stream ID " << connect_ip->stream()->id() + << " and got message status " << MessageStatusToString(message_status); }
diff --git a/quiche/quic/masque/masque_encapsulated_client.cc b/quiche/quic/masque/masque_encapsulated_client.cc index 2d26791..d384363 100644 --- a/quiche/quic/masque/masque_encapsulated_client.cc +++ b/quiche/quic/masque/masque_encapsulated_client.cc
@@ -29,7 +29,8 @@ odd_ = !odd_; } bool IngestData(size_t offset, size_t length) { - quiche::QuicheDataReader reader(writer_.data(), writer_.capacity()); + quiche::QuicheDataReader reader( + writer_.data(), std::min<size_t>(offset + length, writer_.capacity())); if (!reader.Seek(offset) || reader.BytesRemaining() < length) { return false; } @@ -39,7 +40,7 @@ IngestUInt8(first_byte); } // Handle each 16-bit word at a time. - while (reader.BytesRemaining() > sizeof(uint16_t)) { + while (reader.BytesRemaining() >= sizeof(uint16_t)) { uint16_t word; if (!reader.ReadUInt16(&word)) { return false; @@ -57,6 +58,7 @@ while (accumulator_ >> 16 > 0) { accumulator_ = (accumulator_ & 0xffff) + (accumulator_ >> 16); } + accumulator_ = 0xffff & ~accumulator_; quiche::QuicheDataWriter writer2(writer_.capacity(), writer_.data()); return writer2.Seek(offset) && writer2.WriteUInt16(accumulator_); }
diff --git a/quiche/quic/masque/masque_encapsulated_client_session.cc b/quiche/quic/masque/masque_encapsulated_client_session.cc index ababe59..6b76417 100644 --- a/quiche/quic/masque/masque_encapsulated_client_session.cc +++ b/quiche/quic/masque/masque_encapsulated_client_session.cc
@@ -18,9 +18,7 @@ MasqueClientSession* masque_client_session) : QuicSpdyClientSession(config, supported_versions, connection, server_id, crypto_config, push_promise_index), - masque_client_session_(masque_client_session) { - connection->SetMaxPacketLength(1250); -} + masque_client_session_(masque_client_session) {} void MasqueEncapsulatedClientSession::ProcessPacket( absl::string_view packet, QuicSocketAddress server_address) { @@ -50,17 +48,20 @@ QUIC_DLOG(ERROR) << "Dropping empty CONNECT-IP packet"; return; } - const uint8_t ip_version = first_byte >> 8; - absl::string_view quic_packet; + const uint8_t ip_version = first_byte >> 4; quiche::QuicheIpAddress server_ip; if (ip_version == 6) { if (!reader.Seek(5)) { - QUICHE_DLOG(ERROR) << "Failed to seek CONNECT-IP IPv6 start"; + QUICHE_DLOG(ERROR) << "Failed to seek CONNECT-IP IPv6 start" + << "\n" + << quiche::QuicheTextUtils::HexDump(packet); return; } uint8_t next_header = 0; if (!reader.ReadUInt8(&next_header)) { - QUICHE_DLOG(ERROR) << "Failed to read CONNECT-IP next header"; + QUICHE_DLOG(ERROR) << "Failed to read CONNECT-IP next header" + << "\n" + << quiche::QuicheTextUtils::HexDump(packet); return; } if (next_header != 17) { @@ -68,100 +69,140 @@ // do not expect to see them in practice. QUIC_DLOG(ERROR) << "Dropping CONNECT-IP packet with unexpected next header " - << static_cast<int>(next_header); + << static_cast<int>(next_header) << "\n" + << quiche::QuicheTextUtils::HexDump(packet); return; } if (!reader.Seek(1)) { - QUICHE_DLOG(ERROR) << "Failed to seek CONNECT-IP hop limit"; + QUICHE_DLOG(ERROR) << "Failed to seek CONNECT-IP hop limit" + << "\n" + << quiche::QuicheTextUtils::HexDump(packet); return; } absl::string_view source_ip; - if (!reader.ReadBytes(&source_ip, 16)) { - QUICHE_DLOG(ERROR) << "Failed to read CONNECT-IP source IPv6"; + if (!reader.ReadStringPiece(&source_ip, 16)) { + QUICHE_DLOG(ERROR) << "Failed to read CONNECT-IP source IPv6" + << "\n" + << quiche::QuicheTextUtils::HexDump(packet); return; } server_ip.FromPackedString(source_ip.data(), source_ip.length()); if (!reader.Seek(16)) { - QUICHE_DLOG(ERROR) << "Failed to seek CONNECT-IP destination IPv6"; + QUICHE_DLOG(ERROR) << "Failed to seek CONNECT-IP destination IPv6" + << "\n" + << quiche::QuicheTextUtils::HexDump(packet); return; } } else if (ip_version == 4) { uint8_t ihl = first_byte & 0xF; if (ihl < 5) { QUICHE_DLOG(ERROR) << "Dropping CONNECT-IP packet with invalid IHL " - << static_cast<int>(ihl); + << static_cast<int>(ihl) << "\n" + << quiche::QuicheTextUtils::HexDump(packet); return; } if (!reader.Seek(8)) { - QUICHE_DLOG(ERROR) << "Failed to seek CONNECT-IP IPv4 start"; + QUICHE_DLOG(ERROR) << "Failed to seek CONNECT-IP IPv4 start" + << "\n" + << quiche::QuicheTextUtils::HexDump(packet); return; } uint8_t ip_proto = 0; if (!reader.ReadUInt8(&ip_proto)) { - QUICHE_DLOG(ERROR) << "Failed to read CONNECT-IP ip_proto"; + QUICHE_DLOG(ERROR) << "Failed to read CONNECT-IP ip_proto" + << "\n" + << quiche::QuicheTextUtils::HexDump(packet); return; } if (ip_proto != 17) { QUIC_DLOG(ERROR) << "Dropping CONNECT-IP packet with unexpected IP proto " - << static_cast<int>(ip_proto); + << static_cast<int>(ip_proto) << "\n" + << quiche::QuicheTextUtils::HexDump(packet); return; } if (!reader.Seek(2)) { - QUICHE_DLOG(ERROR) << "Failed to seek CONNECT-IP IP checksum"; + QUICHE_DLOG(ERROR) << "Failed to seek CONNECT-IP IP checksum" + << "\n" + << quiche::QuicheTextUtils::HexDump(packet); return; } absl::string_view source_ip; - if (!reader.ReadBytes(&source_ip, 4)) { - QUICHE_DLOG(ERROR) << "Failed to read CONNECT-IP source IPv4"; + if (!reader.ReadStringPiece(&source_ip, 4)) { + QUICHE_DLOG(ERROR) << "Failed to read CONNECT-IP source IPv4" + << "\n" + << quiche::QuicheTextUtils::HexDump(packet); return; } server_ip.FromPackedString(source_ip.data(), source_ip.length()); if (!reader.Seek(4)) { - QUICHE_DLOG(ERROR) << "Failed to seek CONNECT-IP destination IPv4"; + QUICHE_DLOG(ERROR) << "Failed to seek CONNECT-IP destination IPv4" + << "\n" + << quiche::QuicheTextUtils::HexDump(packet); return; } uint8_t ip_options_length = (ihl - 5) * 4; if (!reader.Seek(ip_options_length)) { QUICHE_DLOG(ERROR) << "Failed to seek CONNECT-IP IP options of length " - << static_cast<int>(ip_options_length); + << static_cast<int>(ip_options_length) << "\n" + << quiche::QuicheTextUtils::HexDump(packet); return; } } else { QUIC_DLOG(ERROR) << "Dropping CONNECT-IP packet with unexpected IP version " - << static_cast<int>(ip_version); + << static_cast<int>(ip_version) << "\n" + << quiche::QuicheTextUtils::HexDump(packet); return; } // Parse UDP header. uint16_t server_port; if (!reader.ReadUInt16(&server_port)) { - QUICHE_DLOG(ERROR) << "Failed to read CONNECT-IP source port"; + QUICHE_DLOG(ERROR) << "Failed to read CONNECT-IP source port" + << "\n" + << quiche::QuicheTextUtils::HexDump(packet); return; } if (!reader.Seek(2)) { - QUICHE_DLOG(ERROR) << "Failed to seek CONNECT-IP destination port"; + QUICHE_DLOG(ERROR) << "Failed to seek CONNECT-IP destination port" + << "\n" + << quiche::QuicheTextUtils::HexDump(packet); return; } uint16_t udp_length; if (!reader.ReadUInt16(&udp_length)) { - QUICHE_DLOG(ERROR) << "Failed to read CONNECT-IP UDP length"; + QUICHE_DLOG(ERROR) << "Failed to read CONNECT-IP UDP length" + << "\n" + << quiche::QuicheTextUtils::HexDump(packet); + return; + } + if (udp_length < 8) { + QUICHE_DLOG(ERROR) << "Dropping CONNECT-IP packet with invalid UDP length " + << udp_length << "\n" + << quiche::QuicheTextUtils::HexDump(packet); return; } if (!reader.Seek(2)) { - QUICHE_DLOG(ERROR) << "Failed to seek CONNECT-IP UDP checksum"; + QUICHE_DLOG(ERROR) << "Failed to seek CONNECT-IP UDP checksum" + << "\n" + << quiche::QuicheTextUtils::HexDump(packet); return; } - if (!reader.ReadBytes(&quic_packet, udp_length)) { - QUICHE_DLOG(ERROR) << "Failed to read CONNECT-IP UDP payload"; + absl::string_view quic_packet; + if (!reader.ReadStringPiece(&quic_packet, udp_length - 8)) { + QUICHE_DLOG(ERROR) << "Failed to read CONNECT-IP UDP payload" + << "\n" + << quiche::QuicheTextUtils::HexDump(packet); return; } if (!reader.IsDoneReading()) { - QUICHE_DLOG(INFO) - << "Received CONNECT-IP UDP packet with extra data after payload"; + QUICHE_DLOG(INFO) << "Received CONNECT-IP UDP packet with " + << reader.BytesRemaining() + << " extra bytes after payload\n" + << quiche::QuicheTextUtils::HexDump(packet); } QUIC_DLOG(INFO) << "Received CONNECT-IP encapsulated packet of length " - << packet.size(); + << quic_packet.size(); QuicTime now = connection()->clock()->ApproximateNow(); - QuicReceivedPacket received_packet(quic_packet.data(), quic_packet.length(), + QuicReceivedPacket received_packet(quic_packet.data(), quic_packet.size(), now); QuicSocketAddress server_address = QuicSocketAddress(server_ip, server_port); connection()->ProcessUdpPacket(connection()->self_address(), server_address,
diff --git a/quiche/quic/masque/masque_server_backend.cc b/quiche/quic/masque/masque_server_backend.cc index b42a055..d7ccb9a 100644 --- a/quiche/quic/masque/masque_server_backend.cc +++ b/quiche/quic/masque/masque_server_backend.cc
@@ -13,6 +13,12 @@ const std::string& server_authority, const std::string& cache_directory) : masque_mode_(masque_mode), server_authority_(server_authority) { + // Start with client IP 10.1.1.2. + connect_ip_next_client_ip_[0] = 10; + connect_ip_next_client_ip_[1] = 1; + connect_ip_next_client_ip_[2] = 1; + connect_ip_next_client_ip_[3] = 2; + if (!cache_directory.empty()) { QuicMemoryCacheBackend::InitializeBackend(cache_directory); } @@ -30,7 +36,8 @@ std::string masque_path = ""; auto protocol_pair = request_headers.find(":protocol"); if (method != "CONNECT" || protocol_pair == request_headers.end() || - protocol_pair->second != "connect-udp") { + (protocol_pair->second != "connect-udp" && + protocol_pair->second != "connect-ip")) { // This is not a MASQUE request. return false; } @@ -122,4 +129,25 @@ backend_client_states_.erase(connection_id); } +QuicIpAddress MasqueServerBackend::GetNextClientIpAddress() { + // Makes sure all addresses are in 10.(1-254).(1-254).(2-254) + QuicIpAddress address; + address.FromPackedString( + reinterpret_cast<char*>(&connect_ip_next_client_ip_[0]), + sizeof(connect_ip_next_client_ip_)); + connect_ip_next_client_ip_[3]++; + if (connect_ip_next_client_ip_[3] >= 255) { + connect_ip_next_client_ip_[3] = 2; + connect_ip_next_client_ip_[2]++; + if (connect_ip_next_client_ip_[2] >= 255) { + connect_ip_next_client_ip_[2] = 1; + connect_ip_next_client_ip_[1]++; + if (connect_ip_next_client_ip_[1] >= 255) { + QUIC_LOG(FATAL) << "Ran out of IP addresses, restarting process."; + } + } + } + return address; +} + } // namespace quic
diff --git a/quiche/quic/masque/masque_server_backend.h b/quiche/quic/masque/masque_server_backend.h index cb4b067..50c5a02 100644 --- a/quiche/quic/masque/masque_server_backend.h +++ b/quiche/quic/masque/masque_server_backend.h
@@ -53,6 +53,9 @@ // Unregister backend client. void RemoveBackendClient(QuicConnectionId connection_id); + // Provides a unique client IP address for each CONNECT-IP client. + QuicIpAddress GetNextClientIpAddress(); + private: // Handle MASQUE request. bool MaybeHandleMasqueRequest( @@ -69,6 +72,7 @@ absl::flat_hash_map<QuicConnectionId, BackendClientState, QuicConnectionIdHash> backend_client_states_; + uint8_t connect_ip_next_client_ip_[4]; }; } // namespace quic
diff --git a/quiche/quic/masque/masque_server_session.cc b/quiche/quic/masque/masque_server_session.cc index 03fc326..515d9ab 100644 --- a/quiche/quic/masque/masque_server_session.cc +++ b/quiche/quic/masque/masque_server_session.cc
@@ -4,9 +4,17 @@ #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> #include <limits> #include "absl/cleanup/cleanup.h" @@ -18,8 +26,10 @@ #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/platform/api/quic_ip_address.h" #include "quiche/quic/tools/quic_url.h" #include "quiche/common/platform/api/quiche_url_utils.h" +#include "quiche/common/quiche_ip_address.h" namespace quic { @@ -75,6 +85,79 @@ 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( @@ -94,7 +177,7 @@ // Artificially increase the max packet length to 1350 to ensure we can fit // QUIC packets inside DATAGRAM frames. // TODO(b/181606597) Remove this workaround once we use PMTUD. - connection->SetMaxPacketLength(kDefaultMaxPacketSize); + connection->SetMaxPacketLength(kMasqueMaxOuterPacketSize); masque_server_backend_->RegisterBackendClient(connection_id(), this); QUICHE_DCHECK_NE(event_loop_, nullptr); @@ -123,6 +206,10 @@ [stream_id](const ConnectUdpServerState& connect_udp) { return connect_udp.stream()->id() == stream_id; }); + connect_ip_server_states_.remove_if( + [stream_id](const ConnectIpServerState& connect_ip) { + return connect_ip.stream()->id() == stream_id; + }); QuicSimpleServerSession::OnStreamClosed(stream_id); } @@ -171,11 +258,48 @@ QUIC_DLOG(ERROR) << "MASQUE request with bad method \"" << method << "\""; return CreateBackendErrorResponse("400", "Bad method"); } - if (protocol != "connect-udp") { + if (protocol != "connect-udp" && protocol != "connect-ip") { QUIC_DLOG(ERROR) << "MASQUE request with bad protocol \"" << protocol << "\""; return CreateBackendErrorResponse("400", "Bad protocol"); } + if (protocol == "connect-ip") { + QuicSpdyStream* stream = static_cast<QuicSpdyStream*>( + GetActiveStream(request_handler->stream_id())); + if (stream == nullptr) { + QUIC_BUG(bad masque server stream type) + << "Unexpected stream type for stream ID " + << request_handler->stream_id(); + return CreateBackendErrorResponse("500", "Bad stream type"); + } + QuicIpAddress client_ip = masque_server_backend_->GetNextClientIpAddress(); + QUIC_DLOG(INFO) << "Using client IP " << client_ip.ToString() + << " for CONNECT-IP stream ID " + << request_handler->stream_id(); + int fd = CreateTunInterface(client_ip); + if (fd < 0) { + QUIC_LOG(ERROR) << "Failed to create TUN interface for stream ID " + << request_handler->stream_id(); + return CreateBackendErrorResponse("500", + "Failed to create TUN interface"); + } + if (!event_loop_->RegisterSocket(fd, kSocketEventReadable, this)) { + QUIC_DLOG(ERROR) << "Failed to register TUN fd with the event loop"; + close(fd); + return CreateBackendErrorResponse("500", "Registering TUN socket failed"); + } + connect_ip_server_states_.push_back( + ConnectIpServerState(client_ip, stream, fd, this)); + + spdy::Http2HeaderBlock response_headers; + response_headers[":status"] = "200"; + auto response = std::make_unique<QuicBackendResponse>(); + response->set_response_type(QuicBackendResponse::INCOMPLETE_RESPONSE); + response->set_headers(std::move(response_headers)); + response->set_body(""); + + return response; + } // Extract target host and port from path using default template. std::vector<absl::string_view> path_split = absl::StrSplit(path, '/'); if (path_split.size() != 7 || !path_split[0].empty() || @@ -270,8 +394,37 @@ return connect_udp.fd() == fd; }); if (it == connect_udp_server_states_.end()) { - QUIC_BUG(quic_bug_10974_1) - << "Got unexpected event mask " << events << " on unknown fd " << fd; + auto it2 = absl::c_find_if(connect_ip_server_states_, + [fd](const ConnectIpServerState& connect_ip) { + return connect_ip.fd() == fd; + }); + if (it2 == connect_ip_server_states_.end()) { + QUIC_BUG(quic_bug_10974_1) + << "Got unexpected event mask " << events << " on unknown fd " << fd; + return; + } + + char datagram[1501]; + datagram[0] = 0; // Context ID. + while (true) { + ssize_t read_size = read(fd, datagram + 1, sizeof(datagram) - 1); + if (read_size < 0) { + break; + } + MessageStatus message_status = it2->stream()->SendHttp3Datagram( + absl::string_view(datagram, 1 + read_size)); + QUIC_DVLOG(1) << "Encapsulated IP packet of length " << read_size + << " with stream ID " << it2->stream()->id() + << " and got message status " + << MessageStatusToString(message_status); + } + if (!event_loop_->SupportsEdgeTriggered()) { + if (!event_loop_->RearmSocket(fd, kSocketEventReadable)) { + QUIC_BUG(MasqueServerSession_ConnectIp_OnSocketEvent_Rearm) + << "Failed to re-arm socket " << fd << " for reading"; + } + } + return; } @@ -430,4 +583,123 @@ << target_server_address_ << " with result " << write_result; } +MasqueServerSession::ConnectIpServerState::ConnectIpServerState( + QuicIpAddress client_ip, QuicSpdyStream* stream, QuicUdpSocketFd fd, + MasqueServerSession* masque_session) + : client_ip_(client_ip), + stream_(stream), + fd_(fd), + masque_session_(masque_session) { + QUICHE_DCHECK(client_ip_.IsIPv4()); + QUICHE_DCHECK_NE(fd_, kQuicInvalidSocketFd); + QUICHE_DCHECK_NE(masque_session_, nullptr); + this->stream()->RegisterHttp3DatagramVisitor(this); + this->stream()->RegisterConnectIpVisitor(this); +} + +MasqueServerSession::ConnectIpServerState::~ConnectIpServerState() { + if (stream() != nullptr) { + stream()->UnregisterHttp3DatagramVisitor(); + stream()->UnregisterConnectIpVisitor(); + } + if (fd_ == kQuicInvalidSocketFd) { + return; + } + QuicUdpSocketApi socket_api; + QUIC_DLOG(INFO) << "Closing fd " << fd_; + if (!masque_session_->event_loop()->UnregisterSocket(fd_)) { + QUIC_DLOG(ERROR) << "Failed to unregister FD " << fd_; + } + socket_api.Destroy(fd_); +} + +MasqueServerSession::ConnectIpServerState::ConnectIpServerState( + MasqueServerSession::ConnectIpServerState&& other) { + fd_ = kQuicInvalidSocketFd; + *this = std::move(other); +} + +MasqueServerSession::ConnectIpServerState& +MasqueServerSession::ConnectIpServerState::operator=( + MasqueServerSession::ConnectIpServerState&& other) { + if (fd_ != kQuicInvalidSocketFd) { + QuicUdpSocketApi socket_api; + QUIC_DLOG(INFO) << "Closing fd " << fd_; + if (!masque_session_->event_loop()->UnregisterSocket(fd_)) { + QUIC_DLOG(ERROR) << "Failed to unregister FD " << fd_; + } + socket_api.Destroy(fd_); + } + client_ip_ = other.client_ip_; + stream_ = other.stream_; + other.stream_ = nullptr; + fd_ = other.fd_; + masque_session_ = other.masque_session_; + other.fd_ = kQuicInvalidSocketFd; + if (stream() != nullptr) { + stream()->ReplaceHttp3DatagramVisitor(this); + stream()->ReplaceConnectIpVisitor(this); + } + return *this; +} + +void MasqueServerSession::ConnectIpServerState::OnHttp3Datagram( + QuicStreamId stream_id, absl::string_view payload) { + QUICHE_DCHECK_EQ(stream_id, stream()->id()); + QuicDataReader reader(payload); + uint64_t context_id; + if (!reader.ReadVarInt62(&context_id)) { + QUIC_DLOG(ERROR) << "Failed to read context ID"; + return; + } + if (context_id != 0) { + QUIC_DLOG(ERROR) << "Ignoring HTTP Datagram with unexpected context ID " + << context_id; + return; + } + absl::string_view ip_packet = reader.ReadRemainingPayload(); + ssize_t written = write(fd(), ip_packet.data(), ip_packet.size()); + if (written != static_cast<ssize_t>(ip_packet.size())) { + QUIC_DLOG(ERROR) << "Failed to write CONNECT-IP packet of length " + << ip_packet.size(); + } else { + QUIC_DLOG(INFO) << "Decapsulated CONNECT-IP packet of length " + << ip_packet.size(); + } +} + +bool MasqueServerSession::ConnectIpServerState::OnAddressAssignCapsule( + const AddressAssignCapsule& /*capsule*/) { + return true; +} + +bool MasqueServerSession::ConnectIpServerState::OnAddressRequestCapsule( + const AddressRequestCapsule& /*capsule*/) { + return true; +} + +bool MasqueServerSession::ConnectIpServerState::OnRouteAdvertisementCapsule( + const RouteAdvertisementCapsule& /*capsule*/) { + return true; +} + +void MasqueServerSession::ConnectIpServerState::OnHeadersWritten() { + QUICHE_DCHECK(client_ip_.IsIPv4()) << client_ip_.ToString(); + Capsule address_assign_capsule = Capsule::AddressAssign(); + PrefixWithId assigned_address; + assigned_address.ip_prefix = quiche::QuicheIpPrefix(client_ip_, 32); + assigned_address.request_id = 0; + address_assign_capsule.address_assign_capsule().assigned_addresses.push_back( + assigned_address); + stream()->WriteCapsule(address_assign_capsule); + IpAddressRange default_route; + default_route.start_ip_address.FromString("0.0.0.0"); + default_route.end_ip_address.FromString("255.255.255.255"); + default_route.ip_protocol = 0; + Capsule route_advertisement = Capsule::RouteAdvertisement(); + route_advertisement.route_advertisement_capsule().ip_address_ranges.push_back( + default_route); + stream()->WriteCapsule(route_advertisement); +} + } // namespace quic
diff --git a/quiche/quic/masque/masque_server_session.h b/quiche/quic/masque/masque_server_session.h index f26ef4f..77fdc73 100644 --- a/quiche/quic/masque/masque_server_session.h +++ b/quiche/quic/masque/masque_server_session.h
@@ -90,6 +90,46 @@ MasqueServerSession* masque_session_; // Unowned. }; + // State that the MasqueServerSession keeps for each CONNECT-IP request. + class QUIC_NO_EXPORT ConnectIpServerState + : public QuicSpdyStream::Http3DatagramVisitor, + public QuicSpdyStream::ConnectIpVisitor { + public: + // ConnectIpServerState takes ownership of |fd|. It will unregister it + // from |event_loop| and close the file descriptor when destructed. + explicit ConnectIpServerState(QuicIpAddress client_ip, + QuicSpdyStream* stream, QuicUdpSocketFd fd, + MasqueServerSession* masque_session); + + ~ConnectIpServerState(); + + // Disallow copy but allow move. + ConnectIpServerState(const ConnectIpServerState&) = delete; + ConnectIpServerState(ConnectIpServerState&&); + ConnectIpServerState& operator=(const ConnectIpServerState&) = delete; + ConnectIpServerState& operator=(ConnectIpServerState&&); + + QuicSpdyStream* stream() const { return stream_; } + QuicUdpSocketFd fd() const { return fd_; } + + // From QuicSpdyStream::Http3DatagramVisitor. + void OnHttp3Datagram(QuicStreamId stream_id, + absl::string_view payload) override; + + // From QuicSpdyStream::ConnectIpVisitor. + bool OnAddressAssignCapsule(const AddressAssignCapsule& capsule) override; + bool OnAddressRequestCapsule(const AddressRequestCapsule& capsule) override; + bool OnRouteAdvertisementCapsule( + const RouteAdvertisementCapsule& capsule) override; + void OnHeadersWritten() override; + + private: + QuicIpAddress client_ip_; + QuicSpdyStream* stream_; + QuicUdpSocketFd fd_; // Owned. + MasqueServerSession* masque_session_; // Unowned. + }; + // From QuicSpdySession. bool OnSettingsFrame(const SettingsFrame& frame) override; HttpDatagramSupport LocalHttpDatagramSupport() override { @@ -100,6 +140,7 @@ QuicEventLoop* event_loop_; // Unowned. MasqueMode masque_mode_; std::list<ConnectUdpServerState> connect_udp_server_states_; + std::list<ConnectIpServerState> connect_ip_server_states_; bool masque_initialized_ = false; };
diff --git a/quiche/quic/masque/masque_utils.h b/quiche/quic/masque/masque_utils.h index e55b808..09dbe63 100644 --- a/quiche/quic/masque/masque_utils.h +++ b/quiche/quic/masque/masque_utils.h
@@ -19,7 +19,7 @@ // Maximum packet size for encapsulated connections. enum : QuicByteCount { - kMasqueMaxEncapsulatedPacketSize = 1300, + kMasqueMaxEncapsulatedPacketSize = 1250, kMasqueMaxOuterPacketSize = 1350, };