diff --git a/.github/workflows/ios_tests.yml b/.github/workflows/ios_tests.yml index 763c5b08b3..25d933d017 100644 --- a/.github/workflows/ios_tests.yml +++ b/.github/workflows/ios_tests.yml @@ -31,7 +31,7 @@ jobs: if: steps.check_context.outputs.run_tests == 'true' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: ./bazelw test --test_output=all --config=ios --build_tests_only --config=remote-ci-macos --remote_header="Authorization=Bearer $GITHUB_TOKEN" //test/swift/... + 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/... objctests: name: objc_tests runs-on: macos-11 diff --git a/envoy_build_config/extensions_build_config.bzl b/envoy_build_config/extensions_build_config.bzl index b061112096..c6ef45bc1e 100644 --- a/envoy_build_config/extensions_build_config.bzl +++ b/envoy_build_config/extensions_build_config.bzl @@ -4,6 +4,7 @@ EXTENSION_PACKAGE_VISIBILITY = ["//visibility:public"] EXTENSIONS = { "envoy.clusters.dynamic_forward_proxy": "//source/extensions/clusters/dynamic_forward_proxy:cluster", "envoy.filters.connection_pools.http.generic": "//source/extensions/upstreams/http/generic:config", + "envoy.filters.http.alternate_protocols_cache": "//source/extensions/filters/http/alternate_protocols_cache:config", "envoy.filters.http.assertion": "@envoy_mobile//library/common/extensions/filters/http/assertion:config", "envoy.filters.http.buffer": "//source/extensions/filters/http/buffer:config", "envoy.filters.http.decompressor": "//source/extensions/filters/http/decompressor:config", diff --git a/library/common/config/config.cc b/library/common/config/config.cc index 66696c8b50..c31b9b2618 100644 --- a/library/common/config/config.cc +++ b/library/common/config/config.cc @@ -30,6 +30,14 @@ const char* route_cache_reset_filter_insert = R"( "@type": type.googleapis.com/envoymobile.extensions.filters.http.route_cache_reset.RouteCacheReset )"; +const char* alternate_protocols_cache_filter_insert = R"( + - name: alternate_protocols_cache + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.alternate_protocols_cache.v3.FilterConfig + alternate_protocols_cache_options: + name: default_alternate_protocols_cache +)"; + // clang-format off const std::string config_header = R"( !ignore default_defs: @@ -100,6 +108,17 @@ R"( validation_context: trusted_ca: inline_string: *tls_root_certs +- &base_h3_socket + name: envoy.transport_sockets.quic + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.quic.v3.QuicUpstreamTransport + upstream_tls_context: + common_tls_context: + tls_params: + tls_maximum_protocol_version: TLSv1_3 + validation_context: + trusted_ca: + inline_string: *tls_root_certs )"; const char* config_template = R"( @@ -114,7 +133,7 @@ const char* config_template = R"( name: preserve_case typed_config: "@type": type.googleapis.com/envoy.extensions.http.header_formatters.preserve_case.v3.PreserveCaseFormatterConfig - upstream_http_protocol_options: + upstream_http_protocol_options: &upstream_http_protocol_options auto_sni: true auto_san_validation: true - &h2_protocol_options @@ -126,18 +145,24 @@ const char* config_template = R"( connection_idle_interval: *h2_connection_keepalive_idle_interval timeout: *h2_connection_keepalive_timeout max_concurrent_streams: 100 - upstream_http_protocol_options: - auto_sni: true - auto_san_validation: true + upstream_http_protocol_options: *upstream_http_protocol_options - &alpn_protocol_options envoy.extensions.upstreams.http.v3.HttpProtocolOptions: "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions auto_config: http2_protocol_options: *h2_config http_protocol_options: *h1_config - upstream_http_protocol_options: - auto_sni: true - auto_san_validation: true + upstream_http_protocol_options: *upstream_http_protocol_options +- &h3_protocol_options + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions + auto_config: + alternate_protocols_cache_options: + name: default_alternate_protocols_cache + http3_protocol_options: {} + http2_protocol_options: *h2_config + http_protocol_options: *h1_config + upstream_http_protocol_options: *upstream_http_protocol_options !ignore custom_listener_defs: fake_remote_listener: &fake_remote_listener @@ -378,6 +403,14 @@ R"( upstream_connection_options: *upstream_opts circuit_breakers: *circuit_breakers_settings typed_extension_protocol_options: *h2_protocol_options + - name: base_h3 + connect_timeout: *connect_timeout + lb_policy: CLUSTER_PROVIDED + cluster_type: *base_cluster_type + transport_socket: *base_h3_socket + upstream_connection_options: *upstream_opts + circuit_breakers: *circuit_breakers_settings + typed_extension_protocol_options: *h3_protocol_options stats_flush_interval: *stats_flush_interval stats_sinks: *stats_sinks stats_config: diff --git a/library/common/config/templates.h b/library/common/config/templates.h index 5b51cddeef..fa2e8dfa06 100644 --- a/library/common/config/templates.h +++ b/library/common/config/templates.h @@ -59,6 +59,12 @@ extern const char* fake_remote_cluster_insert; */ extern const char* fake_remote_route_insert; +/** + * Insert that enables the alternate protocols cache filter in the filter chain. + * This is only needed for (currently experimental) QUIC/H3 support. + */ +extern const char* alternate_protocols_cache_filter_insert; + /** * Insert that enables the route cache reset filter in the filter chain. * Should only be added when the route cache should be cleared on every request diff --git a/library/common/http/client.cc b/library/common/http/client.cc index 1a15889f50..8f22a151e8 100644 --- a/library/common/http/client.cc +++ b/library/common/http/client.cc @@ -588,7 +588,7 @@ void Client::removeStream(envoy_stream_t stream_handle) { namespace { const LowerCaseString ClusterHeader{"x-envoy-mobile-cluster"}; -const LowerCaseString H2UpstreamHeader{"x-envoy-mobile-upstream-protocol"}; +const LowerCaseString ProtocolHeader{"x-envoy-mobile-upstream-protocol"}; // Alternate clusters included here are a stopgap to make it less likely for a given connection // 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"}; const char* BaseCluster = "base"; const char* H2Cluster = "base_h2"; +const char* H3Cluster = "base_h3"; const char* ClearTextCluster = "base_clear"; } // namespace @@ -608,14 +609,18 @@ void Client::setDestinationCluster(Http::RequestHeaderMap& headers) { // - Use http/2 or ALPN if requested explicitly via x-envoy-mobile-upstream-protocol. // - Force http/1.1 if request scheme is http (cleartext). const char* cluster{}; - auto h2_header = headers.get(H2UpstreamHeader); + auto protocol_header = headers.get(ProtocolHeader); if (headers.getSchemeValue() == Headers::get().SchemeValues.Http) { cluster = ClearTextCluster; - } else if (!h2_header.empty()) { - ASSERT(h2_header.size() == 1); - const auto value = h2_header[0]->value().getStringView(); + } else if (!protocol_header.empty()) { + ASSERT(protocol_header.size() == 1); + const auto value = protocol_header[0]->value().getStringView(); + // NOTE: This cluster *forces* H2-Raw and does not use ALPN. if (value == "http2") { cluster = H2Cluster; + // NOTE: This cluster will attempt to negotiate H3, but defaults to ALPN over TCP. + } else if (value == "http3") { + cluster = H3Cluster; // FIXME(goaway): No cluster actually forces H1 today except cleartext! } else if (value == "alpn" || value == "http1") { cluster = BaseCluster; @@ -626,8 +631,8 @@ void Client::setDestinationCluster(Http::RequestHeaderMap& headers) { cluster = BaseCluster; } - if (!h2_header.empty()) { - headers.remove(H2UpstreamHeader); + if (!protocol_header.empty()) { + headers.remove(ProtocolHeader); } headers.addCopy(ClusterHeader, std::string{cluster}); diff --git a/library/common/jni/jni_interface.cc b/library/common/jni/jni_interface.cc index 1a5b56074e..9ebe1c0e70 100644 --- a/library/common/jni/jni_interface.cc +++ b/library/common/jni/jni_interface.cc @@ -127,29 +127,29 @@ extern "C" JNIEXPORT void JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibra } extern "C" JNIEXPORT jstring JNICALL -Java_io_envoyproxy_envoymobile_engine_JniLibrary_templateString(JNIEnv* env, - jclass // class -) { +Java_io_envoyproxy_envoymobile_engine_JniLibrary_configTemplate(JNIEnv* env, jclass) { jstring result = env->NewStringUTF(config_template); return result; } extern "C" JNIEXPORT jstring JNICALL -Java_io_envoyproxy_envoymobile_engine_JniLibrary_platformFilterTemplateString(JNIEnv* env, - jclass // class -) { +Java_io_envoyproxy_envoymobile_engine_JniLibrary_platformFilterTemplate(JNIEnv* env, jclass) { jstring result = env->NewStringUTF(platform_filter_template); return result; } extern "C" JNIEXPORT jstring JNICALL -Java_io_envoyproxy_envoymobile_engine_JniLibrary_nativeFilterTemplateString(JNIEnv* env, - jclass // class -) { +Java_io_envoyproxy_envoymobile_engine_JniLibrary_nativeFilterTemplate(JNIEnv* env, jclass) { jstring result = env->NewStringUTF(native_filter_template); return result; } +extern "C" JNIEXPORT jstring JNICALL +Java_io_envoyproxy_envoymobile_engine_JniLibrary_altProtocolCacheFilterInsert(JNIEnv* env, jclass) { + jstring result = env->NewStringUTF(alternate_protocols_cache_filter_insert); + return result; +} + extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibrary_recordCounterInc( JNIEnv* env, jclass, // class diff --git a/library/java/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java b/library/java/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java index 8b65ba721f..8b7df91ceb 100644 --- a/library/java/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java +++ b/library/java/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java @@ -35,6 +35,7 @@ public enum TrustChainVerification { public final String dnsPreresolveHostnames; public final List dnsFallbackNameservers; public final Boolean dnsFilterUnroutableFamilies; + public final Boolean enableHttp3; public final Boolean enableHappyEyeballs; public final Boolean enableInterfaceBinding; public final Integer h2ConnectionKeepaliveIdleIntervalMilliseconds; @@ -69,6 +70,7 @@ public enum TrustChainVerification { * @param dnsPreresolveHostnames hostnames to preresolve on Envoy Client construction. * @param dnsFallbackNameservers addresses to use as DNS name server fallback. * @param dnsFilterUnroutableFamilies whether to filter unroutable IP families or not. + * @param enableHttp3 whether to enable experimental support for HTTP/3 (QUIC). * @param enableHappyEyeballs whether to enable RFC 6555 handling for IPv4/IPv6. * @param enableInterfaceBinding whether to allow interface binding. * @param h2ConnectionKeepaliveIdleIntervalMilliseconds rate in milliseconds seconds to send h2 @@ -92,7 +94,7 @@ public EnvoyConfiguration( int connectTimeoutSeconds, int dnsRefreshSeconds, int dnsFailureRefreshSecondsBase, int dnsFailureRefreshSecondsMax, int dnsQueryTimeoutSeconds, int dnsMinRefreshSeconds, String dnsPreresolveHostnames, List dnsFallbackNameservers, - Boolean dnsFilterUnroutableFamilies, boolean enableHappyEyeballs, + Boolean dnsFilterUnroutableFamilies, boolean enableHttp3, boolean enableHappyEyeballs, boolean enableInterfaceBinding, int h2ConnectionKeepaliveIdleIntervalMilliseconds, int h2ConnectionKeepaliveTimeoutSeconds, List h2RawDomains, int maxConnectionsPerHost, int statsFlushSeconds, int streamIdleTimeoutSeconds, int perTryIdleTimeoutSeconds, @@ -112,6 +114,7 @@ public EnvoyConfiguration( this.dnsPreresolveHostnames = dnsPreresolveHostnames; this.dnsFallbackNameservers = dnsFallbackNameservers; this.dnsFilterUnroutableFamilies = dnsFilterUnroutableFamilies; + this.enableHttp3 = enableHttp3; this.enableHappyEyeballs = enableHappyEyeballs; this.enableInterfaceBinding = enableInterfaceBinding; this.h2ConnectionKeepaliveIdleIntervalMilliseconds = @@ -135,32 +138,37 @@ public EnvoyConfiguration( * Resolves the provided configuration template using properties on this * configuration. * - * @param templateYAML the template configuration to resolve. - * @param platformFilterTemplateYAML helper template to build platform http filters. - * @param nativeFilterTemplateYAML helper template to build native http filters. + * @param configTemplate the template configuration to resolve. + * @param platformFilterTemplate helper template to build platform http filters. + * @param nativeFilterTemplate helper template to build native http filters. + * @param altProtocolCacheFilterInsert helper insert to include the alt protocol cache filter. * @return String, the resolved template. * @throws ConfigurationException, when the template provided is not fully * resolved. */ - String resolveTemplate(final String templateYAML, final String platformFilterTemplateYAML, - final String nativeFilterTemplateYAML) { + String resolveTemplate(final String configTemplate, final String platformFilterTemplate, + final String nativeFilterTemplate, + final String altProtocolCacheFilterInsert) { final StringBuilder customFiltersBuilder = new StringBuilder(); for (EnvoyHTTPFilterFactory filterFactory : httpPlatformFilterFactories) { - String filterConfig = platformFilterTemplateYAML.replace("{{ platform_filter_name }}", - filterFactory.getFilterName()); + String filterConfig = platformFilterTemplate.replace("{{ platform_filter_name }}", + filterFactory.getFilterName()); customFiltersBuilder.append(filterConfig); } for (EnvoyNativeFilterConfig filter : nativeFilterChain) { - String filterConfig = - nativeFilterTemplateYAML.replace("{{ native_filter_name }}", filter.name) - .replace("{{ native_filter_typed_config }}", filter.typedConfig); + String filterConfig = nativeFilterTemplate.replace("{{ native_filter_name }}", filter.name) + .replace("{{ native_filter_typed_config }}", filter.typedConfig); customFiltersBuilder.append(filterConfig); } + if (enableHttp3) { + customFiltersBuilder.append(altProtocolCacheFilterInsert); + } + String processedTemplate = - templateYAML.replace("#{custom_filters}", customFiltersBuilder.toString()); + configTemplate.replace("#{custom_filters}", customFiltersBuilder.toString()); String dnsFallbackNameserversAsString = "[]"; if (!dnsFallbackNameservers.isEmpty()) { diff --git a/library/java/io/envoyproxy/envoymobile/engine/EnvoyEngineImpl.java b/library/java/io/envoyproxy/envoymobile/engine/EnvoyEngineImpl.java index 84c4b665ea..e62ed3d497 100644 --- a/library/java/io/envoyproxy/envoymobile/engine/EnvoyEngineImpl.java +++ b/library/java/io/envoyproxy/envoymobile/engine/EnvoyEngineImpl.java @@ -87,10 +87,11 @@ public int runWithTemplate(String configurationYAML, EnvoyConfiguration envoyCon new JvmStringAccessorContext(entry.getValue())); } - return runWithResolvedYAML(envoyConfiguration.resolveTemplate( - configurationYAML, JniLibrary.platformFilterTemplateString(), - JniLibrary.nativeFilterTemplateString()), - logLevel); + return runWithResolvedYAML( + envoyConfiguration.resolveTemplate(configurationYAML, JniLibrary.platformFilterTemplate(), + JniLibrary.nativeFilterTemplate(), + JniLibrary.altProtocolCacheFilterInsert()), + logLevel); } /** @@ -102,7 +103,7 @@ public int runWithTemplate(String configurationYAML, EnvoyConfiguration envoyCon */ @Override public int runWithConfig(EnvoyConfiguration envoyConfiguration, String logLevel) { - return runWithTemplate(JniLibrary.templateString(), envoyConfiguration, logLevel); + return runWithTemplate(JniLibrary.configTemplate(), envoyConfiguration, logLevel); } private int runWithResolvedYAML(String configurationYAML, String logLevel) { diff --git a/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java b/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java index 5e653f3c83..0729dd1c07 100644 --- a/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java +++ b/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java @@ -191,7 +191,7 @@ protected static native long initEngine(EnvoyOnEngineRunning runningCallback, En * @return A template that may be used as a starting point for constructing * configurations. */ - public static native String templateString(); + public static native String configTemplate(); /** * Increment a counter with the given count. @@ -287,7 +287,7 @@ protected static native int recordHistogramValue(long engine, String elements, b * @return A template that may be used as a starting point for constructing * platform filter configuration. */ - public static native String platformFilterTemplateString(); + public static native String platformFilterTemplate(); /** * 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 * @return A template that may be used as a starting point for constructing * native filter configuration. */ - public static native String nativeFilterTemplateString(); + public static native String nativeFilterTemplate(); + + /** + * Provides a configuration insert that may be used to include an instance + * of the AlternateProtocolsCacheFilter in the filter chain. Needed only + * when (experimental) QUIC/H3 support is enabled. + */ + public static native String altProtocolCacheFilterInsert(); /** * Register a string accessor to get strings from the platform. diff --git a/library/java/org/chromium/net/impl/NativeCronetEngineBuilderImpl.java b/library/java/org/chromium/net/impl/NativeCronetEngineBuilderImpl.java index ef44d0600e..be70c804d2 100644 --- a/library/java/org/chromium/net/impl/NativeCronetEngineBuilderImpl.java +++ b/library/java/org/chromium/net/impl/NativeCronetEngineBuilderImpl.java @@ -60,6 +60,7 @@ public class NativeCronetEngineBuilderImpl extends CronetEngineBuilderImpl { private String mDnsPreresolveHostnames = "[]"; private List mDnsFallbackNameservers = Collections.emptyList(); private boolean mEnableDnsFilterUnroutableFamilies = false; + private boolean mEnableHttp3 = false; private boolean mEnableHappyEyeballs = false; private boolean mEnableInterfaceBinding = false; private int mH2ConnectionKeepaliveIdleIntervalMilliseconds = 100000000; @@ -121,11 +122,11 @@ private EnvoyConfiguration createEnvoyConfiguration() { mAdminInterfaceEnabled, mGrpcStatsDomain, mStatsDPort, mConnectTimeoutSeconds, mDnsRefreshSeconds, mDnsFailureRefreshSecondsBase, mDnsFailureRefreshSecondsMax, mDnsQueryTimeoutSeconds, mDnsMinRefreshSeconds, mDnsPreresolveHostnames, - mDnsFallbackNameservers, mEnableDnsFilterUnroutableFamilies, mEnableHappyEyeballs, - mEnableInterfaceBinding, mH2ConnectionKeepaliveIdleIntervalMilliseconds, - mH2ConnectionKeepaliveTimeoutSeconds, mH2RawDomains, mMaxConnectionsPerHost, - mStatsFlushSeconds, mStreamIdleTimeoutSeconds, mPerTryIdleTimeoutSeconds, mAppVersion, - mAppId, mTrustChainVerification, mVirtualClusters, nativeFilterChain, platformFilterChain, - stringAccessors); + mDnsFallbackNameservers, mEnableDnsFilterUnroutableFamilies, mEnableHttp3, + mEnableHappyEyeballs, mEnableInterfaceBinding, + mH2ConnectionKeepaliveIdleIntervalMilliseconds, mH2ConnectionKeepaliveTimeoutSeconds, + mH2RawDomains, mMaxConnectionsPerHost, mStatsFlushSeconds, mStreamIdleTimeoutSeconds, + mPerTryIdleTimeoutSeconds, mAppVersion, mAppId, mTrustChainVerification, mVirtualClusters, + nativeFilterChain, platformFilterChain, stringAccessors); } } diff --git a/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt b/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt index a830bed430..20a6a1e20f 100644 --- a/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt +++ b/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt @@ -39,6 +39,7 @@ open class EngineBuilder( private var dnsQueryTimeoutSeconds = 25 private var dnsMinRefreshSeconds = 60 private var dnsPreresolveHostnames = "[]" + private var enableHttp3 = false private var enableHappyEyeballs = false private var enableInterfaceBinding = false private var h2ConnectionKeepaliveIdleIntervalMilliseconds = 100000000 @@ -197,6 +198,20 @@ open class EngineBuilder( return this } + /** + * Specify whether to enable experimental HTTP/3 (QUIC) support. Note the actual protocol will + * be negotiated with the upstream endpoint and so upstream support is still required for HTTP/3 + * to be utilized. + * + * @param enableHttp3 whether to enable HTTP/3. + * + * @return This builder. + */ + fun enableHttp3(enableHttp3: Boolean): EngineBuilder { + this.enableHttp3 = enableHttp3 + return this + } + /** * Specify whether to use Happy Eyeballs when multiple IP stacks may be supported. * @@ -475,6 +490,7 @@ open class EngineBuilder( dnsPreresolveHostnames, dnsFallbackNameservers, dnsFilterUnroutableFamilies, + enableHttp3, enableHappyEyeballs, enableInterfaceBinding, h2ConnectionKeepaliveIdleIntervalMilliseconds, diff --git a/test/java/io/envoyproxy/envoymobile/engine/EnvoyConfigurationTest.kt b/test/java/io/envoyproxy/envoymobile/engine/EnvoyConfigurationTest.kt index f386dd67cd..5874624336 100644 --- a/test/java/io/envoyproxy/envoymobile/engine/EnvoyConfigurationTest.kt +++ b/test/java/io/envoyproxy/envoymobile/engine/EnvoyConfigurationTest.kt @@ -24,19 +24,80 @@ private const val NATIVE_FILTER_CONFIG = typed_config: {{ native_filter_typed_config }} """ +private const val APCF_INSERT = +""" + - name: AlternateProtocolsCacheFilter +""" + class EnvoyConfigurationTest { - @Test - fun `resolving with default configuration resolves with values`() { - val envoyConfiguration = EnvoyConfiguration( - false, "stats.foo.com", null, 123, 234, 345, 456, 321, 12, "[hostname]", listOf("8.8.8.8"), - true, true, true, 222, 333, listOf("h2-raw.domain"), 543, 567, 678, 910, "v1.2.3", - "com.mydomain.myapp", TrustChainVerification.ACCEPT_UNTRUSTED, "[test]", - listOf(EnvoyNativeFilterConfig("filter_name", "test_config")), emptyList(), emptyMap() + fun buildTestEnvoyConfiguration( + adminInterfaceEnabled: Boolean = false, + grpcStatsDomain: String = "stats.example.com", + statsDPort: Int? = null, + connectTimeoutSeconds: Int = 123, + dnsRefreshSeconds: Int = 234, + dnsFailureRefreshSecondsBase: Int = 345, + dnsFailureRefreshSecondsMax: Int = 456, + dnsQueryTimeoutSeconds: Int = 321, + dnsMinRefreshSeconds: Int = 12, + dnsPreresolveHostnames: String = "[hostname]", + dnsFallbackNameservers: List = emptyList(), + enableDnsFilterUnroutableFamilies: Boolean = false, + enableHttp3: Boolean = false, + enableHappyEyeballs: Boolean = false, + enableInterfaceBinding: Boolean = false, + h2ConnectionKeepaliveIdleIntervalMilliseconds: Int = 222, + h2ConnectionKeepaliveTimeoutSeconds: Int = 333, + h2RawDomains: List = listOf("h2-raw.example.com"), + maxConnectionsPerHost: Int = 543, + statsFlushSeconds: Int = 567, + streamIdleTimeoutSeconds: Int = 678, + perTryIdleTimeoutSeconds: Int = 910, + appVersion: String = "v1.2.3", + appId: String = "com.example.myapp", + trustChainVerification: TrustChainVerification = TrustChainVerification.VERIFY_TRUST_CHAIN, + virtualClusters: String = "[test]" + ): EnvoyConfiguration { + return EnvoyConfiguration( + adminInterfaceEnabled, + grpcStatsDomain, + statsDPort, + connectTimeoutSeconds, + dnsRefreshSeconds, + dnsFailureRefreshSecondsBase, + dnsFailureRefreshSecondsMax, + dnsQueryTimeoutSeconds, + dnsMinRefreshSeconds, + dnsPreresolveHostnames, + dnsFallbackNameservers, + enableDnsFilterUnroutableFamilies, + enableHttp3, + enableHappyEyeballs, + enableInterfaceBinding, + h2ConnectionKeepaliveIdleIntervalMilliseconds, + h2ConnectionKeepaliveTimeoutSeconds, + h2RawDomains, + maxConnectionsPerHost, + statsFlushSeconds, + streamIdleTimeoutSeconds, + perTryIdleTimeoutSeconds, + appVersion, + appId, + trustChainVerification, + virtualClusters, + listOf(EnvoyNativeFilterConfig("filter_name", "test_config")), + emptyList(), + emptyMap() ) + } + + @Test + fun `configuration resolves with values`() { + val envoyConfiguration = buildTestEnvoyConfiguration() val resolvedTemplate = envoyConfiguration.resolveTemplate( - TEST_CONFIG, PLATFORM_FILTER_CONFIG, NATIVE_FILTER_CONFIG + TEST_CONFIG, PLATFORM_FILTER_CONFIG, NATIVE_FILTER_CONFIG, APCF_INSERT ) assertThat(resolvedTemplate).contains("&connect_timeout 123s") @@ -47,22 +108,25 @@ class EnvoyConfigurationTest { assertThat(resolvedTemplate).contains("&dns_fail_base_interval 345s") assertThat(resolvedTemplate).contains("&dns_fail_max_interval 456s") assertThat(resolvedTemplate).contains("&dns_query_timeout 321s") - assertThat(resolvedTemplate).contains("&dns_lookup_family ALL") - assertThat(resolvedTemplate).contains("&dns_multiple_addresses true") + assertThat(resolvedTemplate).contains("&dns_lookup_family V4_PREFERRED") + assertThat(resolvedTemplate).contains("&dns_multiple_addresses false") assertThat(resolvedTemplate).contains("&dns_min_refresh_rate 12s") assertThat(resolvedTemplate).contains("&dns_preresolve_hostnames [hostname]") assertThat(resolvedTemplate).contains("&dns_resolver_name envoy.network.dns_resolver.cares") - assertThat(resolvedTemplate).contains("&dns_resolver_config {\"@type\":\"type.googleapis.com/envoy.extensions.network.dns_resolver.cares.v3.CaresDnsResolverConfig\",\"resolvers\":[{\"socket_address\":{\"address\":\"8.8.8.8\"}}],\"use_resolvers_as_fallback\": true, \"filter_unroutable_families\": true}") + assertThat(resolvedTemplate).contains("&dns_resolver_config {\"@type\":\"type.googleapis.com/envoy.extensions.network.dns_resolver.cares.v3.CaresDnsResolverConfig\",\"resolvers\":[],\"use_resolvers_as_fallback\": false, \"filter_unroutable_families\": false}") // Interface Binding - assertThat(resolvedTemplate).contains("&enable_interface_binding true") + assertThat(resolvedTemplate).contains("&enable_interface_binding false") // H2 Ping assertThat(resolvedTemplate).contains("&h2_connection_keepalive_idle_interval 0.222s") assertThat(resolvedTemplate).contains("&h2_connection_keepalive_timeout 333s") // H2 Hostnames - assertThat(resolvedTemplate).contains("&h2_raw_domains [\"h2-raw.domain\"]") + assertThat(resolvedTemplate).contains("&h2_raw_domains [\"h2-raw.example.com\"]") + + // H3 + assertThat(resolvedTemplate).doesNotContain(APCF_INSERT); // Per Host Limits assertThat(resolvedTemplate).contains("&max_connections_per_host 543") @@ -70,12 +134,12 @@ class EnvoyConfigurationTest { // Metadata assertThat(resolvedTemplate).contains("os: Android") assertThat(resolvedTemplate).contains("app_version: v1.2.3") - assertThat(resolvedTemplate).contains("app_id: com.mydomain.myapp") + assertThat(resolvedTemplate).contains("app_id: com.example.myapp") assertThat(resolvedTemplate).contains("&virtual_clusters [test]") // Stats - assertThat(resolvedTemplate).contains("&stats_domain stats.foo.com") + assertThat(resolvedTemplate).contains("&stats_domain stats.example.com") assertThat(resolvedTemplate).contains("&stats_flush_interval 567s") // Idle timeouts @@ -83,7 +147,7 @@ class EnvoyConfigurationTest { assertThat(resolvedTemplate).contains("&per_try_idle_timeout 910s") // TlS Verification - assertThat(resolvedTemplate).contains("&trust_chain_verification ACCEPT_UNTRUSTED") + assertThat(resolvedTemplate).contains("&trust_chain_verification VERIFY_TRUST_CHAIN") // Filters assertThat(resolvedTemplate).contains("filter_name") @@ -91,37 +155,37 @@ class EnvoyConfigurationTest { } @Test - fun `resolving with alternate values also sets appropriate config`() { - val envoyConfiguration = EnvoyConfiguration( - false, "stats.foo.com", null, 123, 234, 345, 456, 321, 12, "[hostname]", emptyList(), false, - false, false, 222, 333, emptyList(), 543, 567, 678, 910, "v1.2.3", "com.mydomain.myapp", - TrustChainVerification.ACCEPT_UNTRUSTED, "[test]", - listOf(EnvoyNativeFilterConfig("filter_name", "test_config")), emptyList(), emptyMap() + fun `configuration resolves with alternate values`() { + val envoyConfiguration = buildTestEnvoyConfiguration( + dnsFallbackNameservers = listOf("8.8.8.8"), + enableDnsFilterUnroutableFamilies = true, + enableHappyEyeballs = true, + enableHttp3 = true, + enableInterfaceBinding = true ) val resolvedTemplate = envoyConfiguration.resolveTemplate( - TEST_CONFIG, PLATFORM_FILTER_CONFIG, NATIVE_FILTER_CONFIG + TEST_CONFIG, PLATFORM_FILTER_CONFIG, NATIVE_FILTER_CONFIG, APCF_INSERT ) // DNS - assertThat(resolvedTemplate).contains("&dns_resolver_config {\"@type\":\"type.googleapis.com/envoy.extensions.network.dns_resolver.cares.v3.CaresDnsResolverConfig\",\"resolvers\":[],\"use_resolvers_as_fallback\": false, \"filter_unroutable_families\": false}") - assertThat(resolvedTemplate).contains("&dns_lookup_family V4_PREFERRED") - assertThat(resolvedTemplate).contains("&dns_multiple_addresses false") + assertThat(resolvedTemplate).contains("&dns_resolver_config {\"@type\":\"type.googleapis.com/envoy.extensions.network.dns_resolver.cares.v3.CaresDnsResolverConfig\",\"resolvers\":[{\"socket_address\":{\"address\":\"8.8.8.8\"}}],\"use_resolvers_as_fallback\": true, \"filter_unroutable_families\": true}") + assertThat(resolvedTemplate).contains("&dns_lookup_family ALL") + assertThat(resolvedTemplate).contains("&dns_multiple_addresses true") + + // H3 + assertThat(resolvedTemplate).contains(APCF_INSERT); // Interface Binding - assertThat(resolvedTemplate).contains("&enable_interface_binding false") + assertThat(resolvedTemplate).contains("&enable_interface_binding true") } @Test fun `resolve templates with invalid templates will throw on build`() { - val envoyConfiguration = EnvoyConfiguration( - false, "stats.foo.com", null, 123, 234, 345, 456, 321, 12, "[hostname]", emptyList(), false, - false, false, 123, 123, emptyList(), 543, 567, 678, 910, "v1.2.3", "com.mydomain.myapp", - TrustChainVerification.ACCEPT_UNTRUSTED, "[test]", emptyList(), emptyList(), emptyMap() - ) + val envoyConfiguration = buildTestEnvoyConfiguration() try { - envoyConfiguration.resolveTemplate("{{ missing }}", "", "") + envoyConfiguration.resolveTemplate("{{ missing }}", "", "", "") fail("Unresolved configuration keys should trigger exception.") } catch (e: EnvoyConfiguration.ConfigurationException) { assertThat(e.message).contains("missing") @@ -130,14 +194,13 @@ class EnvoyConfigurationTest { @Test fun `cannot configure both statsD and gRPC stat sink`() { - val envoyConfiguration = EnvoyConfiguration( - false, "stats.foo.com", 5050, 123, 234, 345, 456, 321, 12, "[hostname]", emptyList(), false, - false, false, 123, 123, emptyList(), 543, 567, 678, 910, "v1.2.3", "com.mydomain.myapp", - TrustChainVerification.ACCEPT_UNTRUSTED, "[test]", emptyList(), emptyList(), emptyMap() + val envoyConfiguration = buildTestEnvoyConfiguration( + grpcStatsDomain = "stats.example.com", + statsDPort = 5050 ) try { - envoyConfiguration.resolveTemplate("", "", "") + envoyConfiguration.resolveTemplate("", "", "", "") fail("Conflicting stats keys should trigger exception.") } catch (e: EnvoyConfiguration.ConfigurationException) { assertThat(e.message).contains("cannot enable both statsD and gRPC metrics sink") @@ -146,34 +209,27 @@ class EnvoyConfigurationTest { @Test fun `resolving multiple h2 raw domains`() { - val envoyConfiguration = EnvoyConfiguration( - false, "stats.foo.com", null, 123, 234, 345, 456, 321, 12, "[hostname]", listOf("8.8.8.8"), true, - true, true, 222, 333, listOf("h2-raw.domain", "h2-raw.domain2"), 543, 567, 678, 910, "v1.2.3", - "com.mydomain.myapp", TrustChainVerification.ACCEPT_UNTRUSTED, "[test]", - listOf(EnvoyNativeFilterConfig("filter_name", "test_config")), emptyList(), emptyMap() + val envoyConfiguration = buildTestEnvoyConfiguration( + h2RawDomains = listOf("h2-raw.example.com", "h2-raw.example.com2") ) val resolvedTemplate = envoyConfiguration.resolveTemplate( - TEST_CONFIG, PLATFORM_FILTER_CONFIG, NATIVE_FILTER_CONFIG + TEST_CONFIG, PLATFORM_FILTER_CONFIG, NATIVE_FILTER_CONFIG, APCF_INSERT ) - assertThat(resolvedTemplate).contains("&h2_raw_domains [\"h2-raw.domain\",\"h2-raw.domain2\"]") + assertThat(resolvedTemplate).contains("&h2_raw_domains [\"h2-raw.example.com\",\"h2-raw.example.com2\"]") } @Test fun `resolving multiple dns fallback nameservers`() { - val envoyConfiguration = EnvoyConfiguration( - false, "stats.foo.com", null, 123, 234, 345, 456, 321, 12, "[hostname]", - listOf("8.8.8.8", "1.1.1.1"), true, true, true, 222, 333, - listOf("h2-raw.domain", "h2-raw.domain2"), 543, 567, 678, 910, "v1.2.3", "com.mydomain.myapp", - TrustChainVerification.ACCEPT_UNTRUSTED, "[test]", - listOf(EnvoyNativeFilterConfig("filter_name", "test_config")), emptyList(), emptyMap() + val envoyConfiguration = buildTestEnvoyConfiguration( + dnsFallbackNameservers = listOf("8.8.8.8", "1.1.1.1") ) val resolvedTemplate = envoyConfiguration.resolveTemplate( - TEST_CONFIG, PLATFORM_FILTER_CONFIG, NATIVE_FILTER_CONFIG + TEST_CONFIG, PLATFORM_FILTER_CONFIG, NATIVE_FILTER_CONFIG, APCF_INSERT ) - assertThat(resolvedTemplate).contains("&dns_resolver_config {\"@type\":\"type.googleapis.com/envoy.extensions.network.dns_resolver.cares.v3.CaresDnsResolverConfig\",\"resolvers\":[{\"socket_address\":{\"address\":\"8.8.8.8\"}},{\"socket_address\":{\"address\":\"1.1.1.1\"}}],\"use_resolvers_as_fallback\": true, \"filter_unroutable_families\": true}") + assertThat(resolvedTemplate).contains("&dns_resolver_config {\"@type\":\"type.googleapis.com/envoy.extensions.network.dns_resolver.cares.v3.CaresDnsResolverConfig\",\"resolvers\":[{\"socket_address\":{\"address\":\"8.8.8.8\"}},{\"socket_address\":{\"address\":\"1.1.1.1\"}}],\"use_resolvers_as_fallback\": true, \"filter_unroutable_families\": false}") } }