Implement CONNECT-UDP toy server backend
2 new commandline flags:
* `connect_udp_proxy_targets` sets the allow list of allowed CONNECT-UDP proxy target servers. Must be set for toy server to allow CONNECT-UDP requests.
* `proxy_server_label` sets the server label for proxy error headers per RFC 9209. If not set, one is randomly generated.
PiperOrigin-RevId: 483394501
diff --git a/quiche/quic/tools/connect_server_backend.cc b/quiche/quic/tools/connect_server_backend.cc
index 646f575..cc46571 100644
--- a/quiche/quic/tools/connect_server_backend.cc
+++ b/quiche/quic/tools/connect_server_backend.cc
@@ -13,6 +13,7 @@
#include "quiche/quic/core/quic_server_id.h"
#include "quiche/quic/core/socket_factory.h"
#include "quiche/quic/tools/connect_tunnel.h"
+#include "quiche/quic/tools/connect_udp_tunnel.h"
#include "quiche/quic/tools/quic_simple_server_backend.h"
#include "quiche/common/platform/api/quiche_bug_tracker.h"
#include "quiche/common/platform/api/quiche_logging.h"
@@ -35,15 +36,23 @@
ConnectServerBackend::ConnectServerBackend(
std::unique_ptr<QuicSimpleServerBackend> non_connect_backend,
- absl::flat_hash_set<QuicServerId> acceptable_destinations)
+ absl::flat_hash_set<QuicServerId> acceptable_connect_destinations,
+ absl::flat_hash_set<QuicServerId> acceptable_connect_udp_targets,
+ std::string server_label)
: non_connect_backend_(std::move(non_connect_backend)),
- acceptable_destinations_(std::move(acceptable_destinations)) {
+ acceptable_connect_destinations_(
+ std::move(acceptable_connect_destinations)),
+ acceptable_connect_udp_targets_(
+ std::move(acceptable_connect_udp_targets)),
+ server_label_(std::move(server_label)) {
QUICHE_DCHECK(non_connect_backend_);
+ QUICHE_DCHECK(!server_label_.empty());
}
ConnectServerBackend::~ConnectServerBackend() {
// Expect all streams to be closed before destroying backend.
- QUICHE_DCHECK(tunnels_.empty());
+ QUICHE_DCHECK(connect_tunnels_.empty());
+ QUICHE_DCHECK(connect_udp_tunnels_.empty());
}
bool ConnectServerBackend::InitializeBackend(const std::string&) {
@@ -54,7 +63,8 @@
void ConnectServerBackend::SetSocketFactory(SocketFactory* socket_factory) {
QUICHE_DCHECK_NE(socket_factory_, socket_factory);
- QUICHE_DCHECK(tunnels_.empty());
+ QUICHE_DCHECK(connect_tunnels_.empty());
+ QUICHE_DCHECK(connect_udp_tunnels_.empty());
socket_factory_ = socket_factory;
}
@@ -80,28 +90,42 @@
return;
}
- if (request_headers.contains(":protocol")) {
- // Anything other than normal CONNECT not supported.
- // TODO(ericorth): Add CONNECT-UDP support.
+ if (!request_headers.contains(":protocol")) {
+ // normal CONNECT
+ auto [tunnel_it, inserted] = connect_tunnels_.emplace(
+ request_handler->stream_id(),
+ std::make_unique<ConnectTunnel>(request_handler, socket_factory_,
+ acceptable_connect_destinations_));
+ QUICHE_DCHECK(inserted);
+
+ tunnel_it->second->OpenTunnel(request_headers);
+ } else if (request_headers.find(":protocol")->second == "connect-udp") {
+ // CONNECT-UDP
+ auto [tunnel_it, inserted] = connect_udp_tunnels_.emplace(
+ request_handler->stream_id(),
+ std::make_unique<ConnectUdpTunnel>(request_handler, socket_factory_,
+ server_label_,
+ acceptable_connect_udp_targets_));
+ QUICHE_DCHECK(inserted);
+
+ tunnel_it->second->OpenTunnel(request_headers);
+ } else {
+ // Not a supported request.
non_connect_backend_->HandleConnectHeaders(request_headers,
request_handler);
- return;
}
-
- auto [tunnel_it, inserted] = tunnels_.emplace(
- request_handler->stream_id(),
- std::make_unique<ConnectTunnel>(request_handler, socket_factory_,
- acceptable_destinations_));
- QUICHE_DCHECK(inserted);
-
- tunnel_it->second->OpenTunnel(request_headers);
}
void ConnectServerBackend::HandleConnectData(absl::string_view data,
bool data_complete,
RequestHandler* request_handler) {
- auto tunnel_it = tunnels_.find(request_handler->stream_id());
- if (tunnel_it == tunnels_.end()) {
+ // Expect ConnectUdpTunnels to register a datagram visitor, causing the
+ // stream to process data as capsules. HandleConnectData() should therefore
+ // never be called for streams with a ConnectUdpTunnel.
+ QUICHE_DCHECK(!connect_udp_tunnels_.contains(request_handler->stream_id()));
+
+ auto tunnel_it = connect_tunnels_.find(request_handler->stream_id());
+ if (tunnel_it == connect_tunnels_.end()) {
// If tunnel not found, perhaps it's something being handled for
// non-CONNECT. Possible because this method could be called for anything
// with a ":method":"CONNECT" header, but this class does not handle such
@@ -116,16 +140,22 @@
}
if (data_complete) {
tunnel_it->second->OnClientStreamClose();
- tunnels_.erase(tunnel_it);
+ connect_tunnels_.erase(tunnel_it);
}
}
void ConnectServerBackend::CloseBackendResponseStream(
QuicSimpleServerBackend::RequestHandler* request_handler) {
- auto tunnel_it = tunnels_.find(request_handler->stream_id());
- if (tunnel_it != tunnels_.end()) {
+ auto tunnel_it = connect_tunnels_.find(request_handler->stream_id());
+ if (tunnel_it != connect_tunnels_.end()) {
tunnel_it->second->OnClientStreamClose();
- tunnels_.erase(tunnel_it);
+ connect_tunnels_.erase(tunnel_it);
+ }
+
+ auto udp_tunnel_it = connect_udp_tunnels_.find(request_handler->stream_id());
+ if (udp_tunnel_it != connect_udp_tunnels_.end()) {
+ udp_tunnel_it->second->OnClientStreamClose();
+ connect_udp_tunnels_.erase(udp_tunnel_it);
}
non_connect_backend_->CloseBackendResponseStream(request_handler);
diff --git a/quiche/quic/tools/connect_server_backend.h b/quiche/quic/tools/connect_server_backend.h
index 10eb7c5..c3dcab6 100644
--- a/quiche/quic/tools/connect_server_backend.h
+++ b/quiche/quic/tools/connect_server_backend.h
@@ -15,17 +15,23 @@
#include "quiche/quic/core/quic_server_id.h"
#include "quiche/quic/core/socket_factory.h"
#include "quiche/quic/tools/connect_tunnel.h"
+#include "quiche/quic/tools/connect_udp_tunnel.h"
#include "quiche/quic/tools/quic_simple_server_backend.h"
namespace quic {
-// QUIC server backend that handles CONNECT requests. Non-CONNECT requests are
-// delegated to a separate backend.
+// QUIC server backend that handles CONNECT and CONNECT-UDP requests.
+// Non-CONNECT requests are delegated to a separate backend.
class ConnectServerBackend : public QuicSimpleServerBackend {
public:
+ // `server_label` is an identifier (typically randomly generated) to identify
+ // the server or backend in error headers, per the requirements of RFC 9209,
+ // Section 2.
ConnectServerBackend(
std::unique_ptr<QuicSimpleServerBackend> non_connect_backend,
- absl::flat_hash_set<QuicServerId> acceptable_destinations);
+ absl::flat_hash_set<QuicServerId> acceptable_connect_destinations,
+ absl::flat_hash_set<QuicServerId> acceptable_connect_udp_targets,
+ std::string server_label);
ConnectServerBackend(const ConnectServerBackend&) = delete;
ConnectServerBackend& operator=(const ConnectServerBackend&) = delete;
@@ -48,10 +54,15 @@
private:
std::unique_ptr<QuicSimpleServerBackend> non_connect_backend_;
- const absl::flat_hash_set<QuicServerId> acceptable_destinations_;
+ const absl::flat_hash_set<QuicServerId> acceptable_connect_destinations_;
+ const absl::flat_hash_set<QuicServerId> acceptable_connect_udp_targets_;
+ const std::string server_label_;
SocketFactory* socket_factory_; // unowned
- absl::flat_hash_map<QuicStreamId, std::unique_ptr<ConnectTunnel>> tunnels_;
+ absl::flat_hash_map<QuicStreamId, std::unique_ptr<ConnectTunnel>>
+ connect_tunnels_;
+ absl::flat_hash_map<QuicStreamId, std::unique_ptr<ConnectUdpTunnel>>
+ connect_udp_tunnels_;
};
} // namespace quic
diff --git a/quiche/quic/tools/connect_udp_tunnel.cc b/quiche/quic/tools/connect_udp_tunnel.cc
index ebeef8b..96612fc 100644
--- a/quiche/quic/tools/connect_udp_tunnel.cc
+++ b/quiche/quic/tools/connect_udp_tunnel.cc
@@ -167,14 +167,15 @@
ConnectUdpTunnel::ConnectUdpTunnel(
QuicSimpleServerBackend::RequestHandler* client_stream_request_handler,
- SocketFactory* socket_factory, uint64_t server_label,
+ SocketFactory* socket_factory, std::string server_label,
absl::flat_hash_set<QuicServerId> acceptable_targets)
: acceptable_targets_(std::move(acceptable_targets)),
socket_factory_(socket_factory),
- server_label_(server_label),
+ server_label_(std::move(server_label)),
client_stream_request_handler_(client_stream_request_handler) {
QUICHE_DCHECK(client_stream_request_handler_);
QUICHE_DCHECK(socket_factory_);
+ QUICHE_DCHECK(!server_label_.empty());
}
ConnectUdpTunnel::~ConnectUdpTunnel() {
@@ -385,8 +386,7 @@
spdy::Http2HeaderBlock headers;
headers[":status"] = status;
- structured_headers::Item proxy_status_item(
- absl::StrCat("QuicToyServer", server_label_));
+ structured_headers::Item proxy_status_item(server_label_);
structured_headers::Item proxy_status_error_item(
std::string{proxy_status_error});
structured_headers::Item proxy_status_details_item(
diff --git a/quiche/quic/tools/connect_udp_tunnel.h b/quiche/quic/tools/connect_udp_tunnel.h
index f254b61..71819df 100644
--- a/quiche/quic/tools/connect_udp_tunnel.h
+++ b/quiche/quic/tools/connect_udp_tunnel.h
@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#ifndef QUICHE_QUIC_TOOLS_CONNECT_TUNNEL_H_
-#define QUICHE_QUIC_TOOLS_CONNECT_TUNNEL_H_
+#ifndef QUICHE_QUIC_TOOLS_CONNECT_UDP_TUNNEL_H_
+#define QUICHE_QUIC_TOOLS_CONNECT_UDP_TUNNEL_H_
#include <cstdint>
#include <memory>
@@ -36,7 +36,7 @@
// per the requirements of RFC 9209, Section 2.
ConnectUdpTunnel(
QuicSimpleServerBackend::RequestHandler* client_stream_request_handler,
- SocketFactory* socket_factory, uint64_t server_label,
+ SocketFactory* socket_factory, std::string server_label,
absl::flat_hash_set<QuicServerId> acceptable_targets);
~ConnectUdpTunnel();
ConnectUdpTunnel(const ConnectUdpTunnel&) = delete;
@@ -80,7 +80,7 @@
const absl::flat_hash_set<QuicServerId> acceptable_targets_;
SocketFactory* const socket_factory_;
- const uint64_t server_label_;
+ const std::string server_label_;
// Null when client stream closed.
QuicSimpleServerBackend::RequestHandler* client_stream_request_handler_;
@@ -94,4 +94,4 @@
} // namespace quic
-#endif // QUICHE_QUIC_TOOLS_CONNECT_TUNNEL_H_
+#endif // QUICHE_QUIC_TOOLS_CONNECT_UDP_TUNNEL_H_
diff --git a/quiche/quic/tools/connect_udp_tunnel_test.cc b/quiche/quic/tools/connect_udp_tunnel_test.cc
index 23b1c11..9d7e88c 100644
--- a/quiche/quic/tools/connect_udp_tunnel_test.cc
+++ b/quiche/quic/tools/connect_udp_tunnel_test.cc
@@ -142,7 +142,7 @@
ConnectUdpTunnel tunnel_{
&request_handler_,
&socket_factory_,
- /*server_label=*/123,
+ "server_label",
/*acceptable_targets=*/
{{std::string(kAcceptableTarget), kAcceptablePort},
{TestLoopback4().ToString(), kAcceptablePort},
diff --git a/quiche/quic/tools/quic_toy_server.cc b/quiche/quic/tools/quic_toy_server.cc
index 132ecce..3f23af6 100644
--- a/quiche/quic/tools/quic_toy_server.cc
+++ b/quiche/quic/tools/quic_toy_server.cc
@@ -4,10 +4,12 @@
#include "quiche/quic/tools/quic_toy_server.h"
+#include <string>
#include <utility>
#include <vector>
#include "absl/container/flat_hash_set.h"
+#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "quiche/quic/core/quic_server_id.h"
@@ -18,6 +20,7 @@
#include "quiche/quic/tools/quic_memory_cache_backend.h"
#include "quiche/common/platform/api/quiche_command_line_flags.h"
#include "quiche/common/platform/api/quiche_logging.h"
+#include "quiche/common/quiche_random.h"
DEFINE_QUICHE_COMMAND_LINE_FLAG(int32_t, port, 6121,
"The port the quic server will listen on.");
@@ -48,7 +51,20 @@
DEFINE_QUICHE_COMMAND_LINE_FLAG(
std::string, connect_proxy_destinations, "",
"Specifies a comma-separated list of destinations (\"hostname:port\") to "
- "which the quic server will allow tunneling via CONNECT.");
+ "which the QUIC server will allow tunneling via CONNECT.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+ std::string, connect_udp_proxy_targets, "",
+ "Specifies a comma-separated list of target servers (\"hostname:port\") to "
+ "which the QUIC server will allow tunneling via CONNECT-UDP.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+ std::string, proxy_server_label, "",
+ "Specifies an identifier to identify the server in proxy error headers, "
+ "per the requirements of RFC 9209, Section 2. It should uniquely identify "
+ "the running service between separate running instances of the QUIC toy "
+ "server binary. If not specified, one will be randomly generated as "
+ "\"QuicToyServerN\" where N is a random uint64_t.");
namespace quic {
@@ -68,6 +84,8 @@
}
if (!quiche::GetQuicheCommandLineFlag(FLAGS_connect_proxy_destinations)
+ .empty() ||
+ !quiche::GetQuicheCommandLineFlag(FLAGS_connect_udp_proxy_targets)
.empty()) {
absl::flat_hash_set<QuicServerId> connect_proxy_destinations;
for (absl::string_view destination : absl::StrSplit(
@@ -79,10 +97,31 @@
connect_proxy_destinations.insert(
std::move(destination_server_id).value());
}
- QUICHE_CHECK(!connect_proxy_destinations.empty());
+
+ absl::flat_hash_set<QuicServerId> connect_udp_proxy_targets;
+ for (absl::string_view target : absl::StrSplit(
+ quiche::GetQuicheCommandLineFlag(FLAGS_connect_udp_proxy_targets),
+ ',', absl::SkipEmpty())) {
+ absl::optional<QuicServerId> target_server_id =
+ QuicServerId::ParseFromHostPortString(target);
+ QUICHE_CHECK(target_server_id.has_value());
+ connect_udp_proxy_targets.insert(std::move(target_server_id).value());
+ }
+
+ QUICHE_CHECK(!connect_proxy_destinations.empty() ||
+ !connect_udp_proxy_targets.empty());
+
+ std::string proxy_server_label =
+ quiche::GetQuicheCommandLineFlag(FLAGS_proxy_server_label);
+ if (proxy_server_label.empty()) {
+ proxy_server_label = absl::StrCat(
+ "QuicToyServer",
+ quiche::QuicheRandom::GetInstance()->InsecureRandUint64());
+ }
return std::make_unique<ConnectServerBackend>(
- std::move(memory_cache_backend), std::move(connect_proxy_destinations));
+ std::move(memory_cache_backend), std::move(connect_proxy_destinations),
+ std::move(connect_udp_proxy_targets), std::move(proxy_server_label));
}
return memory_cache_backend;