Skip to content

Commit 245a045

Browse files
authored
quic: support half-way server's preferred address (envoyproxy#25356)
Signed-off-by: Dan Zhang <[email protected]>
1 parent b2601c5 commit 245a045

30 files changed

+560
-26
lines changed

api/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ proto_library(
268268
"//envoy/extensions/quic/connection_id_generator/v3:pkg",
269269
"//envoy/extensions/quic/crypto_stream/v3:pkg",
270270
"//envoy/extensions/quic/proof_source/v3:pkg",
271+
"//envoy/extensions/quic/server_preferred_address/v3:pkg",
271272
"//envoy/extensions/rate_limit_descriptors/expr/v3:pkg",
272273
"//envoy/extensions/rbac/matchers/upstream_ip_port/v3:pkg",
273274
"//envoy/extensions/regex_engines/v3:pkg",

api/envoy/config/listener/v3/quic_config.proto

+10-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import "envoy/config/core/v3/protocol.proto";
99
import "google/protobuf/duration.proto";
1010
import "google/protobuf/wrappers.proto";
1111

12+
import "xds/annotations/v3/status.proto";
13+
1214
import "udpa/annotations/status.proto";
1315
import "udpa/annotations/versioning.proto";
1416
import "validate/validate.proto";
@@ -22,7 +24,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE;
2224
// [#protodoc-title: QUIC listener config]
2325

2426
// Configuration specific to the UDP QUIC listener.
25-
// [#next-free-field: 9]
27+
// [#next-free-field: 10]
2628
message QuicProtocolOptions {
2729
option (udpa.annotations.versioning).previous_message_type =
2830
"envoy.api.v2.listener.QuicProtocolOptions";
@@ -68,4 +70,11 @@ message QuicProtocolOptions {
6870
// If not specified the :ref:`default one configured by <envoy_v3_api_msg_extensions.quic.connection_id_generator.v3.DeterministicConnectionIdGeneratorConfig>` will be used.
6971
// [#extension-category: envoy.quic.connection_id_generator]
7072
core.v3.TypedExtensionConfig connection_id_generator_config = 8;
73+
74+
// Configure the server's preferred address to advertise so that client can migrate to it. See :ref:`example <envoy_v3_api_msg_extensions.quic.server_preferred_address.v3.FixedServerPreferredAddressConfig>` which configures a pair of v4 and v6 preferred addresses.
75+
// The current QUICHE implementation will advertise only one of the preferred IPv4 and IPv6 addresses based on the address family the client initially connects with, and only if the client is also QUICHE-based.
76+
// If not specified, Envoy will not advertise any server's preferred address.
77+
// [#extension-category: envoy.quic.server_preferred_address]
78+
core.v3.TypedExtensionConfig server_preferred_address_config = 9
79+
[(xds.annotations.v3.field_status).work_in_progress = true];
7180
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py.
2+
3+
load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package")
4+
5+
licenses(["notice"]) # Apache 2
6+
7+
api_proto_package(
8+
deps = [
9+
"@com_github_cncf_udpa//udpa/annotations:pkg",
10+
"@com_github_cncf_udpa//xds/annotations/v3:pkg",
11+
],
12+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
syntax = "proto3";
2+
3+
package envoy.extensions.quic.server_preferred_address.v3;
4+
5+
import "xds/annotations/v3/status.proto";
6+
7+
import "udpa/annotations/status.proto";
8+
9+
option java_package = "io.envoyproxy.envoy.extensions.quic.server_preferred_address.v3";
10+
option java_outer_classname = "FixedServerPreferredAddressConfigProto";
11+
option java_multiple_files = true;
12+
option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/quic/server_preferred_address/v3;server_preferred_addressv3";
13+
option (udpa.annotations.file_status).package_version_status = ACTIVE;
14+
15+
// [#protodoc-title: QUIC server preferred address config]
16+
// [#extension: envoy.quic.server_preferred_address.fixed]
17+
18+
// Configuration for FixedServerPreferredAddressConfig.
19+
message FixedServerPreferredAddressConfig {
20+
// [#comment:TODO(danzh2010): discuss with API shepherds before removing WiP status.]
21+
22+
option (xds.annotations.v3.message_status).work_in_progress = true;
23+
24+
oneof ipv4_type {
25+
// String representation of IPv4 address, i.e. "127.0.0.2".
26+
// If not specified, none will be configured.
27+
string ipv4_address = 1;
28+
}
29+
30+
oneof ipv6_type {
31+
// String representation of IPv6 address, i.e. "::1".
32+
// If not specified, none will be configured.
33+
string ipv6_address = 2;
34+
}
35+
}

api/versioning/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ proto_library(
207207
"//envoy/extensions/quic/connection_id_generator/v3:pkg",
208208
"//envoy/extensions/quic/crypto_stream/v3:pkg",
209209
"//envoy/extensions/quic/proof_source/v3:pkg",
210+
"//envoy/extensions/quic/server_preferred_address/v3:pkg",
210211
"//envoy/extensions/rate_limit_descriptors/expr/v3:pkg",
211212
"//envoy/extensions/rbac/matchers/upstream_ip_port/v3:pkg",
212213
"//envoy/extensions/regex_engines/v3:pkg",

docs/root/api-v3/config/quic/quic_extensions.rst

+1
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ Quic extensions
88
../../extensions/quic/crypto_stream/v3/*
99
../../extensions/quic/proof_source/v3/*
1010
../../extensions/quic/connection_id_generator/v3/*
11+
../../extensions/quic/server_preferred_address/v3/*

source/common/quic/BUILD

+12
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,7 @@ envoy_cc_library(
399399
":envoy_quic_packet_writer_lib",
400400
":envoy_quic_proof_source_factory_interface",
401401
":envoy_quic_proof_source_lib",
402+
":envoy_quic_server_preferred_address_config_factory_interface",
402403
":envoy_quic_utils_lib",
403404
"//envoy/network:listener_interface",
404405
"//source/common/network:listener_lib",
@@ -592,6 +593,17 @@ envoy_cc_library(
592593
],
593594
)
594595

596+
envoy_cc_library(
597+
name = "envoy_quic_server_preferred_address_config_factory_interface",
598+
hdrs = ["envoy_quic_server_preferred_address_config_factory.h"],
599+
tags = ["nofips"],
600+
deps = [
601+
"//envoy/config:typed_config_interface",
602+
"//envoy/network:address_interface",
603+
"@com_github_google_quiche//:quic_platform_socket_address",
604+
],
605+
)
606+
595607
envoy_cc_library(
596608
name = "quic_stats_gatherer",
597609
srcs = ["quic_stats_gatherer.cc"],

source/common/quic/active_quic_listener.cc

+34
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,20 @@ ActiveQuicListenerFactory::ActiveQuicListenerFactory(
287287
*Config::Utility::translateToFactoryConfig(cid_generator_config, validation_visitor,
288288
cid_generator_config_factory));
289289

290+
if (config.has_server_preferred_address_config()) {
291+
const envoy::config::core::v3::TypedExtensionConfig& server_preferred_address_config =
292+
config.server_preferred_address_config();
293+
auto& server_preferred_address_config_factory =
294+
Config::Utility::getAndCheckFactory<EnvoyQuicServerPreferredAddressConfigFactory>(
295+
server_preferred_address_config);
296+
server_preferred_address_config_ =
297+
server_preferred_address_config_factory.createServerPreferredAddressConfig(
298+
*Config::Utility::translateToFactoryConfig(config.server_preferred_address_config(),
299+
validation_visitor,
300+
server_preferred_address_config_factory),
301+
validation_visitor);
302+
}
303+
290304
#if defined(SO_ATTACH_REUSEPORT_CBPF) && defined(__linux__)
291305
if (!disable_kernel_bpf_packet_routing_for_test_) {
292306
if (concurrency_ > 1) {
@@ -312,6 +326,26 @@ Network::ConnectionHandler::ActiveUdpListenerPtr ActiveQuicListenerFactory::crea
312326
Network::SocketSharedPtr&& listen_socket_ptr, Event::Dispatcher& disptacher,
313327
Network::ListenerConfig& config) {
314328
ASSERT(crypto_server_stream_factory_.has_value());
329+
if (server_preferred_address_config_ != nullptr) {
330+
std::pair<quic::QuicSocketAddress, quic::QuicSocketAddress> addresses =
331+
server_preferred_address_config_->getServerPreferredAddresses(
332+
listen_socket_ptr->connectionInfoProvider().localAddress());
333+
quic::QuicSocketAddress v4_address = addresses.first;
334+
if (v4_address.IsInitialized()) {
335+
ENVOY_BUG(v4_address.host().address_family() == quiche::IpAddressFamily::IP_V4,
336+
absl::StrCat("Configured IPv4 server's preferred address isn't a v4 address:",
337+
v4_address.ToString()));
338+
quic_config_.SetIPv4AlternateServerAddressToSend(v4_address);
339+
}
340+
quic::QuicSocketAddress v6_address = addresses.second;
341+
if (v6_address.IsInitialized()) {
342+
ENVOY_BUG(v6_address.host().address_family() == quiche::IpAddressFamily::IP_V6,
343+
absl::StrCat("Configured IPv6 server's preferred address isn't a v6 address:",
344+
v4_address.ToString()));
345+
quic_config_.SetIPv6AlternateServerAddressToSend(v6_address);
346+
}
347+
}
348+
315349
return std::make_unique<ActiveQuicListener>(
316350
runtime, worker_index, concurrency_, disptacher, parent, std::move(listen_socket_ptr), config,
317351
quic_config_, kernel_worker_routing_, enabled_, quic_stat_names_,

source/common/quic/active_quic_listener.h

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "source/common/quic/envoy_quic_connection_id_generator_factory.h"
1111
#include "source/common/quic/envoy_quic_dispatcher.h"
1212
#include "source/common/quic/envoy_quic_proof_source_factory_interface.h"
13+
#include "source/common/quic/envoy_quic_server_preferred_address_config_factory.h"
1314
#include "source/common/runtime/runtime_protos.h"
1415
#include "source/server/active_udp_listener.h"
1516

@@ -117,6 +118,7 @@ class ActiveQuicListenerFactory : public Network::ActiveUdpListenerFactory,
117118
absl::optional<std::reference_wrapper<EnvoyQuicProofSourceFactoryInterface>>
118119
proof_source_factory_;
119120
EnvoyQuicConnectionIdGeneratorFactoryPtr quic_cid_generator_factory_;
121+
EnvoyQuicServerPreferredAddressConfigPtr server_preferred_address_config_;
120122
quic::QuicConfig quic_config_;
121123
const uint32_t concurrency_;
122124
envoy::config::core::v3::RuntimeFeatureFlag enabled_;

source/common/quic/envoy_quic_client_connection.cc

+23-6
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,11 @@ void EnvoyQuicClientConnection::maybeMigratePort() {
127127
return;
128128
}
129129

130+
probeWithNewPort(peer_address(), quic::PathValidationReason::kPortMigration);
131+
}
132+
133+
void EnvoyQuicClientConnection::probeWithNewPort(const quic::QuicSocketAddress& peer_address,
134+
quic::PathValidationReason reason) {
130135
const Network::Address::InstanceConstSharedPtr& current_local_address =
131136
connectionSocket()->connectionInfoProvider().localAddress();
132137
// Creates an IP address with unset port. The port will be set when the new socket is created.
@@ -148,13 +153,11 @@ void EnvoyQuicClientConnection::maybeMigratePort() {
148153
std::make_unique<Network::UdpDefaultWriter>(probing_socket->ioHandle()));
149154
quic::QuicSocketAddress self_address = envoyIpAddressToQuicSocketAddress(
150155
probing_socket->connectionInfoProvider().localAddress()->ip());
151-
quic::QuicSocketAddress peer_address = envoyIpAddressToQuicSocketAddress(
152-
probing_socket->connectionInfoProvider().remoteAddress()->ip());
153156

154157
auto context = std::make_unique<EnvoyQuicPathValidationContext>(
155158
self_address, peer_address, std::move(writer), std::move(probing_socket));
156159
ValidatePath(std::move(context), std::make_unique<EnvoyPathValidationResultDelegate>(*this),
157-
quic::PathValidationReason::kPortMigration);
160+
reason);
158161
}
159162

160163
void EnvoyQuicClientConnection::onPathValidationSuccess(
@@ -163,8 +166,16 @@ void EnvoyQuicClientConnection::onPathValidationSuccess(
163166
static_cast<EnvoyQuicClientConnection::EnvoyQuicPathValidationContext*>(context.get());
164167

165168
auto probing_socket = envoy_context->releaseSocket();
166-
if (MigratePath(envoy_context->self_address(), envoy_context->peer_address(),
167-
envoy_context->releaseWriter(), true)) {
169+
if (envoy_context->peer_address() != peer_address()) {
170+
OnServerPreferredAddressValidated(*envoy_context, true);
171+
envoy_context->releaseWriter();
172+
} else {
173+
MigratePath(envoy_context->self_address(), envoy_context->peer_address(),
174+
envoy_context->releaseWriter(), true);
175+
}
176+
177+
if (self_address() == envoy_context->self_address() &&
178+
peer_address() == envoy_context->peer_address()) {
168179
// probing_socket will be set as the new default socket. But old sockets are still able to
169180
// receive packets.
170181
setConnectionSocket(std::move(probing_socket));
@@ -231,7 +242,7 @@ void EnvoyQuicClientConnection::setNumPtosForPortMigration(uint32_t num_ptos_for
231242
}
232243

233244
EnvoyQuicClientConnection::EnvoyQuicPathValidationContext::EnvoyQuicPathValidationContext(
234-
quic::QuicSocketAddress& self_address, quic::QuicSocketAddress& peer_address,
245+
const quic::QuicSocketAddress& self_address, const quic::QuicSocketAddress& peer_address,
235246
std::unique_ptr<EnvoyQuicPacketWriter> writer,
236247
std::unique_ptr<Network::ConnectionSocket> probing_socket)
237248
: QuicPathValidationContext(self_address, peer_address), writer_(std::move(writer)),
@@ -277,5 +288,11 @@ void EnvoyQuicClientConnection::OnCanWrite() {
277288
onWriteEventDone();
278289
}
279290

291+
void EnvoyQuicClientConnection::probeAndMigrateToServerPreferredAddress(
292+
const quic::QuicSocketAddress& server_preferred_address) {
293+
probeWithNewPort(server_preferred_address,
294+
quic::PathValidationReason::kServerPreferredAddressMigration);
295+
}
296+
280297
} // namespace Quic
281298
} // namespace Envoy

source/common/quic/envoy_quic_client_connection.h

+10-2
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,17 @@ class EnvoyQuicClientConnection : public quic::QuicConnection,
8282

8383
void setNumPtosForPortMigration(uint32_t num_ptos_for_path_degrading);
8484

85+
// Probes the given peer address. If the probing succeeds, start sending packets to this address
86+
// instead.
87+
void
88+
probeAndMigrateToServerPreferredAddress(const quic::QuicSocketAddress& server_preferred_address);
89+
8590
private:
8691
// Holds all components needed for a QUIC connection probing/migration.
8792
class EnvoyQuicPathValidationContext : public quic::QuicPathValidationContext {
8893
public:
89-
EnvoyQuicPathValidationContext(quic::QuicSocketAddress& self_address,
90-
quic::QuicSocketAddress& peer_address,
94+
EnvoyQuicPathValidationContext(const quic::QuicSocketAddress& self_address,
95+
const quic::QuicSocketAddress& peer_address,
9196
std::unique_ptr<EnvoyQuicPacketWriter> writer,
9297
std::unique_ptr<Network::ConnectionSocket> probing_socket);
9398

@@ -131,6 +136,9 @@ class EnvoyQuicClientConnection : public quic::QuicConnection,
131136

132137
void maybeMigratePort();
133138

139+
void probeWithNewPort(const quic::QuicSocketAddress& peer_address,
140+
quic::PathValidationReason reason);
141+
134142
OptRef<PacketsToReadDelegate> delegate_;
135143
uint32_t packets_dropped_{0};
136144
Event::Dispatcher& dispatcher_;

source/common/quic/envoy_quic_client_session.cc

+5
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,11 @@ void EnvoyQuicClientSession::OnNewEncryptionKeyAvailable(
254254
raiseConnectionEvent(Network::ConnectionEvent::ConnectedZeroRtt);
255255
}
256256
}
257+
void EnvoyQuicClientSession::OnServerPreferredAddressAvailable(
258+
const quic::QuicSocketAddress& server_preferred_address) {
259+
static_cast<EnvoyQuicClientConnection*>(connection())
260+
->probeAndMigrateToServerPreferredAddress(server_preferred_address);
261+
}
257262

258263
} // namespace Quic
259264
} // namespace Envoy

source/common/quic/envoy_quic_client_session.h

+3
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ class EnvoyQuicClientSession : public QuicFilterManagerConnectionImpl,
8282
// Notify any registered connection pool when new streams are available.
8383
void OnCanCreateNewOutgoingStream(bool) override;
8484

85+
void OnServerPreferredAddressAvailable(
86+
const quic::QuicSocketAddress& server_preferred_address) override;
87+
8588
using quic::QuicSpdyClientSession::PerformActionOnActiveStreams;
8689

8790
protected:

source/common/quic/envoy_quic_dispatcher.cc

+5-2
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ EnvoyQuicDispatcher::EnvoyQuicDispatcher(
6060
listener_stats_(listener_stats), per_worker_stats_(per_worker_stats), dispatcher_(dispatcher),
6161
listen_socket_(listen_socket), quic_stat_names_(quic_stat_names),
6262
crypto_server_stream_factory_(crypto_server_stream_factory),
63-
quic_stats_(generateStats(listener_config.listenerScope())) {}
63+
quic_stats_(generateStats(listener_config.listenerScope())),
64+
connection_stats_({QUIC_CONNECTION_STATS(
65+
POOL_COUNTER_PREFIX(listener_config.listenerScope(), "quic.connection"))}) {}
6466

6567
void EnvoyQuicDispatcher::OnConnectionClosed(quic::QuicConnectionId connection_id,
6668
quic::QuicErrorCode error,
@@ -101,7 +103,8 @@ std::unique_ptr<quic::QuicSession> EnvoyQuicDispatcher::CreateQuicSession(
101103
quic_config, quic::ParsedQuicVersionVector{version}, std::move(quic_connection), this,
102104
session_helper(), crypto_config(), compressed_certs_cache(), dispatcher_,
103105
listener_config_->perConnectionBufferLimitBytes(), quic_stat_names_,
104-
listener_config_->listenerScope(), crypto_server_stream_factory_, std::move(stream_info));
106+
listener_config_->listenerScope(), crypto_server_stream_factory_, std::move(stream_info),
107+
connection_stats_);
105108
if (filter_chain != nullptr) {
106109
// Setup filter chain before Initialize().
107110
const bool has_filter_initialized =

source/common/quic/envoy_quic_dispatcher.h

+1
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ class EnvoyQuicDispatcher : public quic::QuicDispatcher {
9393
EnvoyQuicCryptoServerStreamFactoryInterface& crypto_server_stream_factory_;
9494
FilterChainToConnectionMap connections_by_filter_chain_;
9595
QuicDispatcherStats quic_stats_;
96+
QuicConnectionStats connection_stats_;
9697
};
9798

9899
} // namespace Quic
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#pragma once
2+
3+
#include "envoy/config/typed_config.h"
4+
#include "envoy/network/address.h"
5+
#include "envoy/protobuf/message_validator.h"
6+
7+
#include "quiche/quic/platform/api/quic_socket_address.h"
8+
9+
namespace Envoy {
10+
namespace Quic {
11+
12+
// An interface to provide the server's preferred addresses at runtime.
13+
class EnvoyQuicServerPreferredAddressConfig {
14+
public:
15+
virtual ~EnvoyQuicServerPreferredAddressConfig() = default;
16+
17+
/**
18+
* Called during config loading.
19+
* @param local_address the configured default listening address.
20+
* Returns a pair of the server preferred addresses in form of {IPv4, IPv6} which will be used for
21+
* the entire life time of the QUIC listener. An uninitialized address value means no preferred
22+
* address for that address family.
23+
*/
24+
virtual std::pair<quic::QuicSocketAddress, quic::QuicSocketAddress>
25+
getServerPreferredAddresses(const Network::Address::InstanceConstSharedPtr& local_address) PURE;
26+
};
27+
28+
using EnvoyQuicServerPreferredAddressConfigPtr =
29+
std::unique_ptr<EnvoyQuicServerPreferredAddressConfig>;
30+
31+
class EnvoyQuicServerPreferredAddressConfigFactory : public Config::TypedFactory {
32+
public:
33+
std::string category() const override { return "envoy.quic.server_preferred_address"; }
34+
35+
/**
36+
* Returns an EnvoyQuicServerPreferredAddressConfig object according to the given server preferred
37+
* address config.
38+
*/
39+
virtual EnvoyQuicServerPreferredAddressConfigPtr
40+
createServerPreferredAddressConfig(const Protobuf::Message& config,
41+
ProtobufMessage::ValidationVisitor& validation_visitor) PURE;
42+
};
43+
44+
} // namespace Quic
45+
} // namespace Envoy

0 commit comments

Comments
 (0)