Skip to content

Commit 256fe5f

Browse files
goawayalyssawilk
andauthored
h3/quic: add experimental option to the Android/JVM EngineBuilder (#2163)
Description: Adds *experimental* support for negotiating H3/QUIC connections. To enable this, `enableHttp3` must be set to true on the EngineBuilder AND the header "x-envoy-mobile-upstream-protocol" must be set to "http3" on the request. If the upstream does not support H3, the standard ALPN over TCP configuration will still be used by default. Risk Level: Moderate Testing: Updated Unit Coverage Co-authored-by: Alyssa Wilk <[email protected]> Signed-off-by: Mike Schore <[email protected]>
1 parent 1b34577 commit 256fe5f

File tree

12 files changed

+237
-103
lines changed

12 files changed

+237
-103
lines changed

.github/workflows/ios_tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
if: steps.check_context.outputs.run_tests == 'true'
3232
env:
3333
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
34-
run: ./bazelw test --test_output=all --config=ios --build_tests_only --config=remote-ci-macos --remote_header="Authorization=Bearer $GITHUB_TOKEN" //test/swift/...
34+
run: ./bazelw test --experimental_ui_max_stdouterr_bytes=10485760 --test_output=all --config=ios --build_tests_only --config=remote-ci-macos --remote_header="Authorization=Bearer $GITHUB_TOKEN" //test/swift/...
3535
objctests:
3636
name: objc_tests
3737
runs-on: macos-11

envoy_build_config/extensions_build_config.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ EXTENSION_PACKAGE_VISIBILITY = ["//visibility:public"]
44
EXTENSIONS = {
55
"envoy.clusters.dynamic_forward_proxy": "//source/extensions/clusters/dynamic_forward_proxy:cluster",
66
"envoy.filters.connection_pools.http.generic": "//source/extensions/upstreams/http/generic:config",
7+
"envoy.filters.http.alternate_protocols_cache": "//source/extensions/filters/http/alternate_protocols_cache:config",
78
"envoy.filters.http.assertion": "@envoy_mobile//library/common/extensions/filters/http/assertion:config",
89
"envoy.filters.http.buffer": "//source/extensions/filters/http/buffer:config",
910
"envoy.filters.http.decompressor": "//source/extensions/filters/http/decompressor:config",

library/common/config/config.cc

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ const char* route_cache_reset_filter_insert = R"(
3030
"@type": type.googleapis.com/envoymobile.extensions.filters.http.route_cache_reset.RouteCacheReset
3131
)";
3232

33+
const char* alternate_protocols_cache_filter_insert = R"(
34+
- name: alternate_protocols_cache
35+
typed_config:
36+
"@type": type.googleapis.com/envoy.extensions.filters.http.alternate_protocols_cache.v3.FilterConfig
37+
alternate_protocols_cache_options:
38+
name: default_alternate_protocols_cache
39+
)";
40+
3341
// clang-format off
3442
const std::string config_header = R"(
3543
!ignore default_defs:
@@ -100,6 +108,17 @@ R"(
100108
validation_context:
101109
trusted_ca:
102110
inline_string: *tls_root_certs
111+
- &base_h3_socket
112+
name: envoy.transport_sockets.quic
113+
typed_config:
114+
"@type": type.googleapis.com/envoy.extensions.transport_sockets.quic.v3.QuicUpstreamTransport
115+
upstream_tls_context:
116+
common_tls_context:
117+
tls_params:
118+
tls_maximum_protocol_version: TLSv1_3
119+
validation_context:
120+
trusted_ca:
121+
inline_string: *tls_root_certs
103122
)";
104123

105124
const char* config_template = R"(
@@ -114,7 +133,7 @@ const char* config_template = R"(
114133
name: preserve_case
115134
typed_config:
116135
"@type": type.googleapis.com/envoy.extensions.http.header_formatters.preserve_case.v3.PreserveCaseFormatterConfig
117-
upstream_http_protocol_options:
136+
upstream_http_protocol_options: &upstream_http_protocol_options
118137
auto_sni: true
119138
auto_san_validation: true
120139
- &h2_protocol_options
@@ -126,18 +145,24 @@ const char* config_template = R"(
126145
connection_idle_interval: *h2_connection_keepalive_idle_interval
127146
timeout: *h2_connection_keepalive_timeout
128147
max_concurrent_streams: 100
129-
upstream_http_protocol_options:
130-
auto_sni: true
131-
auto_san_validation: true
148+
upstream_http_protocol_options: *upstream_http_protocol_options
132149
- &alpn_protocol_options
133150
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
134151
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
135152
auto_config:
136153
http2_protocol_options: *h2_config
137154
http_protocol_options: *h1_config
138-
upstream_http_protocol_options:
139-
auto_sni: true
140-
auto_san_validation: true
155+
upstream_http_protocol_options: *upstream_http_protocol_options
156+
- &h3_protocol_options
157+
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
158+
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
159+
auto_config:
160+
alternate_protocols_cache_options:
161+
name: default_alternate_protocols_cache
162+
http3_protocol_options: {}
163+
http2_protocol_options: *h2_config
164+
http_protocol_options: *h1_config
165+
upstream_http_protocol_options: *upstream_http_protocol_options
141166
142167
!ignore custom_listener_defs:
143168
fake_remote_listener: &fake_remote_listener
@@ -378,6 +403,14 @@ R"(
378403
upstream_connection_options: *upstream_opts
379404
circuit_breakers: *circuit_breakers_settings
380405
typed_extension_protocol_options: *h2_protocol_options
406+
- name: base_h3
407+
connect_timeout: *connect_timeout
408+
lb_policy: CLUSTER_PROVIDED
409+
cluster_type: *base_cluster_type
410+
transport_socket: *base_h3_socket
411+
upstream_connection_options: *upstream_opts
412+
circuit_breakers: *circuit_breakers_settings
413+
typed_extension_protocol_options: *h3_protocol_options
381414
stats_flush_interval: *stats_flush_interval
382415
stats_sinks: *stats_sinks
383416
stats_config:

library/common/config/templates.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ extern const char* fake_remote_cluster_insert;
5959
*/
6060
extern const char* fake_remote_route_insert;
6161

62+
/**
63+
* Insert that enables the alternate protocols cache filter in the filter chain.
64+
* This is only needed for (currently experimental) QUIC/H3 support.
65+
*/
66+
extern const char* alternate_protocols_cache_filter_insert;
67+
6268
/**
6369
* Insert that enables the route cache reset filter in the filter chain.
6470
* Should only be added when the route cache should be cleared on every request

library/common/http/client.cc

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,7 @@ void Client::removeStream(envoy_stream_t stream_handle) {
588588
namespace {
589589

590590
const LowerCaseString ClusterHeader{"x-envoy-mobile-cluster"};
591-
const LowerCaseString H2UpstreamHeader{"x-envoy-mobile-upstream-protocol"};
591+
const LowerCaseString ProtocolHeader{"x-envoy-mobile-upstream-protocol"};
592592

593593
// Alternate clusters included here are a stopgap to make it less likely for a given connection
594594
// class to suffer "catastrophic" failure of all outbound requests due to a network blip, by
@@ -598,6 +598,7 @@ const LowerCaseString H2UpstreamHeader{"x-envoy-mobile-upstream-protocol"};
598598

599599
const char* BaseCluster = "base";
600600
const char* H2Cluster = "base_h2";
601+
const char* H3Cluster = "base_h3";
601602
const char* ClearTextCluster = "base_clear";
602603

603604
} // namespace
@@ -608,14 +609,18 @@ void Client::setDestinationCluster(Http::RequestHeaderMap& headers) {
608609
// - Use http/2 or ALPN if requested explicitly via x-envoy-mobile-upstream-protocol.
609610
// - Force http/1.1 if request scheme is http (cleartext).
610611
const char* cluster{};
611-
auto h2_header = headers.get(H2UpstreamHeader);
612+
auto protocol_header = headers.get(ProtocolHeader);
612613
if (headers.getSchemeValue() == Headers::get().SchemeValues.Http) {
613614
cluster = ClearTextCluster;
614-
} else if (!h2_header.empty()) {
615-
ASSERT(h2_header.size() == 1);
616-
const auto value = h2_header[0]->value().getStringView();
615+
} else if (!protocol_header.empty()) {
616+
ASSERT(protocol_header.size() == 1);
617+
const auto value = protocol_header[0]->value().getStringView();
618+
// NOTE: This cluster *forces* H2-Raw and does not use ALPN.
617619
if (value == "http2") {
618620
cluster = H2Cluster;
621+
// NOTE: This cluster will attempt to negotiate H3, but defaults to ALPN over TCP.
622+
} else if (value == "http3") {
623+
cluster = H3Cluster;
619624
// FIXME(goaway): No cluster actually forces H1 today except cleartext!
620625
} else if (value == "alpn" || value == "http1") {
621626
cluster = BaseCluster;
@@ -626,8 +631,8 @@ void Client::setDestinationCluster(Http::RequestHeaderMap& headers) {
626631
cluster = BaseCluster;
627632
}
628633

629-
if (!h2_header.empty()) {
630-
headers.remove(H2UpstreamHeader);
634+
if (!protocol_header.empty()) {
635+
headers.remove(ProtocolHeader);
631636
}
632637

633638
headers.addCopy(ClusterHeader, std::string{cluster});

library/common/jni/jni_interface.cc

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -127,29 +127,29 @@ extern "C" JNIEXPORT void JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibra
127127
}
128128

129129
extern "C" JNIEXPORT jstring JNICALL
130-
Java_io_envoyproxy_envoymobile_engine_JniLibrary_templateString(JNIEnv* env,
131-
jclass // class
132-
) {
130+
Java_io_envoyproxy_envoymobile_engine_JniLibrary_configTemplate(JNIEnv* env, jclass) {
133131
jstring result = env->NewStringUTF(config_template);
134132
return result;
135133
}
136134

137135
extern "C" JNIEXPORT jstring JNICALL
138-
Java_io_envoyproxy_envoymobile_engine_JniLibrary_platformFilterTemplateString(JNIEnv* env,
139-
jclass // class
140-
) {
136+
Java_io_envoyproxy_envoymobile_engine_JniLibrary_platformFilterTemplate(JNIEnv* env, jclass) {
141137
jstring result = env->NewStringUTF(platform_filter_template);
142138
return result;
143139
}
144140

145141
extern "C" JNIEXPORT jstring JNICALL
146-
Java_io_envoyproxy_envoymobile_engine_JniLibrary_nativeFilterTemplateString(JNIEnv* env,
147-
jclass // class
148-
) {
142+
Java_io_envoyproxy_envoymobile_engine_JniLibrary_nativeFilterTemplate(JNIEnv* env, jclass) {
149143
jstring result = env->NewStringUTF(native_filter_template);
150144
return result;
151145
}
152146

147+
extern "C" JNIEXPORT jstring JNICALL
148+
Java_io_envoyproxy_envoymobile_engine_JniLibrary_altProtocolCacheFilterInsert(JNIEnv* env, jclass) {
149+
jstring result = env->NewStringUTF(alternate_protocols_cache_filter_insert);
150+
return result;
151+
}
152+
153153
extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibrary_recordCounterInc(
154154
JNIEnv* env,
155155
jclass, // class

library/java/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public enum TrustChainVerification {
3535
public final String dnsPreresolveHostnames;
3636
public final List<String> dnsFallbackNameservers;
3737
public final Boolean dnsFilterUnroutableFamilies;
38+
public final Boolean enableHttp3;
3839
public final Boolean enableHappyEyeballs;
3940
public final Boolean enableInterfaceBinding;
4041
public final Integer h2ConnectionKeepaliveIdleIntervalMilliseconds;
@@ -69,6 +70,7 @@ public enum TrustChainVerification {
6970
* @param dnsPreresolveHostnames hostnames to preresolve on Envoy Client construction.
7071
* @param dnsFallbackNameservers addresses to use as DNS name server fallback.
7172
* @param dnsFilterUnroutableFamilies whether to filter unroutable IP families or not.
73+
* @param enableHttp3 whether to enable experimental support for HTTP/3 (QUIC).
7274
* @param enableHappyEyeballs whether to enable RFC 6555 handling for IPv4/IPv6.
7375
* @param enableInterfaceBinding whether to allow interface binding.
7476
* @param h2ConnectionKeepaliveIdleIntervalMilliseconds rate in milliseconds seconds to send h2
@@ -92,7 +94,7 @@ public EnvoyConfiguration(
9294
int connectTimeoutSeconds, int dnsRefreshSeconds, int dnsFailureRefreshSecondsBase,
9395
int dnsFailureRefreshSecondsMax, int dnsQueryTimeoutSeconds, int dnsMinRefreshSeconds,
9496
String dnsPreresolveHostnames, List<String> dnsFallbackNameservers,
95-
Boolean dnsFilterUnroutableFamilies, boolean enableHappyEyeballs,
97+
Boolean dnsFilterUnroutableFamilies, boolean enableHttp3, boolean enableHappyEyeballs,
9698
boolean enableInterfaceBinding, int h2ConnectionKeepaliveIdleIntervalMilliseconds,
9799
int h2ConnectionKeepaliveTimeoutSeconds, List<String> h2RawDomains, int maxConnectionsPerHost,
98100
int statsFlushSeconds, int streamIdleTimeoutSeconds, int perTryIdleTimeoutSeconds,
@@ -112,6 +114,7 @@ public EnvoyConfiguration(
112114
this.dnsPreresolveHostnames = dnsPreresolveHostnames;
113115
this.dnsFallbackNameservers = dnsFallbackNameservers;
114116
this.dnsFilterUnroutableFamilies = dnsFilterUnroutableFamilies;
117+
this.enableHttp3 = enableHttp3;
115118
this.enableHappyEyeballs = enableHappyEyeballs;
116119
this.enableInterfaceBinding = enableInterfaceBinding;
117120
this.h2ConnectionKeepaliveIdleIntervalMilliseconds =
@@ -135,32 +138,37 @@ public EnvoyConfiguration(
135138
* Resolves the provided configuration template using properties on this
136139
* configuration.
137140
*
138-
* @param templateYAML the template configuration to resolve.
139-
* @param platformFilterTemplateYAML helper template to build platform http filters.
140-
* @param nativeFilterTemplateYAML helper template to build native http filters.
141+
* @param configTemplate the template configuration to resolve.
142+
* @param platformFilterTemplate helper template to build platform http filters.
143+
* @param nativeFilterTemplate helper template to build native http filters.
144+
* @param altProtocolCacheFilterInsert helper insert to include the alt protocol cache filter.
141145
* @return String, the resolved template.
142146
* @throws ConfigurationException, when the template provided is not fully
143147
* resolved.
144148
*/
145-
String resolveTemplate(final String templateYAML, final String platformFilterTemplateYAML,
146-
final String nativeFilterTemplateYAML) {
149+
String resolveTemplate(final String configTemplate, final String platformFilterTemplate,
150+
final String nativeFilterTemplate,
151+
final String altProtocolCacheFilterInsert) {
147152
final StringBuilder customFiltersBuilder = new StringBuilder();
148153

149154
for (EnvoyHTTPFilterFactory filterFactory : httpPlatformFilterFactories) {
150-
String filterConfig = platformFilterTemplateYAML.replace("{{ platform_filter_name }}",
151-
filterFactory.getFilterName());
155+
String filterConfig = platformFilterTemplate.replace("{{ platform_filter_name }}",
156+
filterFactory.getFilterName());
152157
customFiltersBuilder.append(filterConfig);
153158
}
154159

155160
for (EnvoyNativeFilterConfig filter : nativeFilterChain) {
156-
String filterConfig =
157-
nativeFilterTemplateYAML.replace("{{ native_filter_name }}", filter.name)
158-
.replace("{{ native_filter_typed_config }}", filter.typedConfig);
161+
String filterConfig = nativeFilterTemplate.replace("{{ native_filter_name }}", filter.name)
162+
.replace("{{ native_filter_typed_config }}", filter.typedConfig);
159163
customFiltersBuilder.append(filterConfig);
160164
}
161165

166+
if (enableHttp3) {
167+
customFiltersBuilder.append(altProtocolCacheFilterInsert);
168+
}
169+
162170
String processedTemplate =
163-
templateYAML.replace("#{custom_filters}", customFiltersBuilder.toString());
171+
configTemplate.replace("#{custom_filters}", customFiltersBuilder.toString());
164172

165173
String dnsFallbackNameserversAsString = "[]";
166174
if (!dnsFallbackNameservers.isEmpty()) {

library/java/io/envoyproxy/envoymobile/engine/EnvoyEngineImpl.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,11 @@ public int runWithTemplate(String configurationYAML, EnvoyConfiguration envoyCon
8787
new JvmStringAccessorContext(entry.getValue()));
8888
}
8989

90-
return runWithResolvedYAML(envoyConfiguration.resolveTemplate(
91-
configurationYAML, JniLibrary.platformFilterTemplateString(),
92-
JniLibrary.nativeFilterTemplateString()),
93-
logLevel);
90+
return runWithResolvedYAML(
91+
envoyConfiguration.resolveTemplate(configurationYAML, JniLibrary.platformFilterTemplate(),
92+
JniLibrary.nativeFilterTemplate(),
93+
JniLibrary.altProtocolCacheFilterInsert()),
94+
logLevel);
9495
}
9596

9697
/**
@@ -102,7 +103,7 @@ public int runWithTemplate(String configurationYAML, EnvoyConfiguration envoyCon
102103
*/
103104
@Override
104105
public int runWithConfig(EnvoyConfiguration envoyConfiguration, String logLevel) {
105-
return runWithTemplate(JniLibrary.templateString(), envoyConfiguration, logLevel);
106+
return runWithTemplate(JniLibrary.configTemplate(), envoyConfiguration, logLevel);
106107
}
107108

108109
private int runWithResolvedYAML(String configurationYAML, String logLevel) {

library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ protected static native long initEngine(EnvoyOnEngineRunning runningCallback, En
191191
* @return A template that may be used as a starting point for constructing
192192
* configurations.
193193
*/
194-
public static native String templateString();
194+
public static native String configTemplate();
195195

196196
/**
197197
* Increment a counter with the given count.
@@ -287,7 +287,7 @@ protected static native int recordHistogramValue(long engine, String elements, b
287287
* @return A template that may be used as a starting point for constructing
288288
* platform filter configuration.
289289
*/
290-
public static native String platformFilterTemplateString();
290+
public static native String platformFilterTemplate();
291291

292292
/**
293293
* Provides a configuration template that may be used for building native
@@ -296,7 +296,14 @@ protected static native int recordHistogramValue(long engine, String elements, b
296296
* @return A template that may be used as a starting point for constructing
297297
* native filter configuration.
298298
*/
299-
public static native String nativeFilterTemplateString();
299+
public static native String nativeFilterTemplate();
300+
301+
/**
302+
* Provides a configuration insert that may be used to include an instance
303+
* of the AlternateProtocolsCacheFilter in the filter chain. Needed only
304+
* when (experimental) QUIC/H3 support is enabled.
305+
*/
306+
public static native String altProtocolCacheFilterInsert();
300307

301308
/**
302309
* Register a string accessor to get strings from the platform.

library/java/org/chromium/net/impl/NativeCronetEngineBuilderImpl.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public class NativeCronetEngineBuilderImpl extends CronetEngineBuilderImpl {
6060
private String mDnsPreresolveHostnames = "[]";
6161
private List<String> mDnsFallbackNameservers = Collections.emptyList();
6262
private boolean mEnableDnsFilterUnroutableFamilies = false;
63+
private boolean mEnableHttp3 = false;
6364
private boolean mEnableHappyEyeballs = false;
6465
private boolean mEnableInterfaceBinding = false;
6566
private int mH2ConnectionKeepaliveIdleIntervalMilliseconds = 100000000;
@@ -121,11 +122,11 @@ private EnvoyConfiguration createEnvoyConfiguration() {
121122
mAdminInterfaceEnabled, mGrpcStatsDomain, mStatsDPort, mConnectTimeoutSeconds,
122123
mDnsRefreshSeconds, mDnsFailureRefreshSecondsBase, mDnsFailureRefreshSecondsMax,
123124
mDnsQueryTimeoutSeconds, mDnsMinRefreshSeconds, mDnsPreresolveHostnames,
124-
mDnsFallbackNameservers, mEnableDnsFilterUnroutableFamilies, mEnableHappyEyeballs,
125-
mEnableInterfaceBinding, mH2ConnectionKeepaliveIdleIntervalMilliseconds,
126-
mH2ConnectionKeepaliveTimeoutSeconds, mH2RawDomains, mMaxConnectionsPerHost,
127-
mStatsFlushSeconds, mStreamIdleTimeoutSeconds, mPerTryIdleTimeoutSeconds, mAppVersion,
128-
mAppId, mTrustChainVerification, mVirtualClusters, nativeFilterChain, platformFilterChain,
129-
stringAccessors);
125+
mDnsFallbackNameservers, mEnableDnsFilterUnroutableFamilies, mEnableHttp3,
126+
mEnableHappyEyeballs, mEnableInterfaceBinding,
127+
mH2ConnectionKeepaliveIdleIntervalMilliseconds, mH2ConnectionKeepaliveTimeoutSeconds,
128+
mH2RawDomains, mMaxConnectionsPerHost, mStatsFlushSeconds, mStreamIdleTimeoutSeconds,
129+
mPerTryIdleTimeoutSeconds, mAppVersion, mAppId, mTrustChainVerification, mVirtualClusters,
130+
nativeFilterChain, platformFilterChain, stringAccessors);
130131
}
131132
}

0 commit comments

Comments
 (0)