From 7ca7daffb72d7fdfeadd1cfb6d85f0ee0a8ee695 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Wed, 24 Apr 2024 12:43:50 -0700 Subject: [PATCH 01/22] quic: support for server-preferred address behind DNAT Signed-off-by: Greg Greenway --- .../quic/server_preferred_address/v3/BUILD | 1 + ...ixed_server_preferred_address_config.proto | 33 +++++ changelogs/current.yaml | 4 + source/common/quic/active_quic_listener.cc | 37 ++++-- ..._server_preferred_address_config_factory.h | 16 ++- .../quic/server_preferred_address/BUILD | 1 + .../fixed_server_preferred_address_config.cc | 117 +++++++++++++++--- .../fixed_server_preferred_address_config.h | 18 ++- .../filters/test_socket_interface.cc | 45 +++++-- .../filters/test_socket_interface.h | 31 +++-- .../integration/quic_http_integration_test.cc | 77 ++++++++++++ test/integration/socket_interface_swap.cc | 32 ++--- test/integration/socket_interface_swap.h | 28 ++++- 13 files changed, 373 insertions(+), 67 deletions(-) diff --git a/api/envoy/extensions/quic/server_preferred_address/v3/BUILD b/api/envoy/extensions/quic/server_preferred_address/v3/BUILD index d49202b74ab4..628f71321fba 100644 --- a/api/envoy/extensions/quic/server_preferred_address/v3/BUILD +++ b/api/envoy/extensions/quic/server_preferred_address/v3/BUILD @@ -6,6 +6,7 @@ licenses(["notice"]) # Apache 2 api_proto_package( deps = [ + "//envoy/config/core/v3:pkg", "@com_github_cncf_xds//udpa/annotations:pkg", "@com_github_cncf_xds//xds/annotations/v3:pkg", ], diff --git a/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto b/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto index b500263a9a6c..8286052c836e 100644 --- a/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto +++ b/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto @@ -2,6 +2,8 @@ syntax = "proto3"; package envoy.extensions.quic.server_preferred_address.v3; +import "envoy/config/core/v3/address.proto"; + import "xds/annotations/v3/status.proto"; import "udpa/annotations/status.proto"; @@ -23,13 +25,44 @@ message FixedServerPreferredAddressConfig { oneof ipv4_type { // String representation of IPv4 address, i.e. "127.0.0.2". + // The listener's port will be used. // If not specified, none will be configured. string ipv4_address = 1; + + + // The IPv4 address to advertise to clients for Server Preferred Address. + envoy.config.core.v3.SocketAddress ipv4_address_and_port = 3; + } + + oneof ipv4_dnat_type { + // If there is a DNAT between the client and Envoy, the address that Envoy will observe + // server preferred address IPv4 packets being sent to. If this is not specified, it is assumed + // there is no DNAT and the server preferred address packets will be sent to the address advertised + // to clients for server preferred address. + // + // String representation of IPv4 address, i.e. "127.0.0.2" + // The listener's port will be used. + string ipv4_dnat_address = 5; } oneof ipv6_type { // String representation of IPv6 address, i.e. "::1". + // The listener's port will be used. // If not specified, none will be configured. string ipv6_address = 2; + + + // The IPv6 address to advertise to clients for Server Preferred Address. + envoy.config.core.v3.SocketAddress ipv6_address_and_port = 4; + } + oneof ipv6_dnat_type { + // If there is a DNAT between the client and Envoy, the address that Envoy will observe + // server preferred address IPv6 packets being sent to. If this is not specified, it is assumed + // there is no DNAT and the server preferred address packets will be sent to the address advertised + // to clients for server preferred address. + // + // String representation of IPv6 address, i.e. "::1" + // The listener's port will be used. + string ipv6_dnat_address = 6; } } diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 816b41c0c959..3a51ca52c474 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -47,5 +47,9 @@ new_features: change: | Added :ref:`Filter State Input ` for matching http input based on filter state objects. +- area: quic + change: | + Added support for QUIC server preferred address when there is a DNAT between the client and Envoy. See + :ref:`new config `. deprecated: diff --git a/source/common/quic/active_quic_listener.cc b/source/common/quic/active_quic_listener.cc index 57db1441bc6f..079b18153ad4 100644 --- a/source/common/quic/active_quic_listener.cc +++ b/source/common/quic/active_quic_listener.cc @@ -343,22 +343,37 @@ Network::ConnectionHandler::ActiveUdpListenerPtr ActiveQuicListenerFactory::crea Network::ListenerConfig& config) { ASSERT(crypto_server_stream_factory_.has_value()); if (server_preferred_address_config_ != nullptr) { - std::pair addresses = + const EnvoyQuicServerPreferredAddressConfig::Addresses addresses = server_preferred_address_config_->getServerPreferredAddresses( listen_socket_ptr->connectionInfoProvider().localAddress()); - quic::QuicSocketAddress v4_address = addresses.first; - if (v4_address.IsInitialized()) { - ENVOY_BUG(v4_address.host().address_family() == quiche::IpAddressFamily::IP_V4, + if (addresses.ipv4_.IsInitialized()) { + ENVOY_BUG(addresses.ipv4_.host().address_family() == quiche::IpAddressFamily::IP_V4, absl::StrCat("Configured IPv4 server's preferred address isn't a v4 address:", - v4_address.ToString())); - quic_config_.SetIPv4AlternateServerAddressToSend(v4_address); + addresses.ipv4_.ToString())); + if (addresses.dnat_ipv4_.IsInitialized()) { + ENVOY_BUG( + addresses.dnat_ipv4_.host().address_family() == quiche::IpAddressFamily::IP_V4, + absl::StrCat("Configured IPv4 server's preferred DNAT address isn't a v4 address:", + addresses.dnat_ipv4_.ToString())); + quic_config_.SetIPv4AlternateServerAddressForDNat(addresses.ipv4_, addresses.dnat_ipv4_); + } else { + quic_config_.SetIPv4AlternateServerAddressToSend(addresses.ipv4_); + } } - quic::QuicSocketAddress v6_address = addresses.second; - if (v6_address.IsInitialized()) { - ENVOY_BUG(v6_address.host().address_family() == quiche::IpAddressFamily::IP_V6, + + if (addresses.ipv6_.IsInitialized()) { + ENVOY_BUG(addresses.ipv6_.host().address_family() == quiche::IpAddressFamily::IP_V6, absl::StrCat("Configured IPv6 server's preferred address isn't a v6 address:", - v4_address.ToString())); - quic_config_.SetIPv6AlternateServerAddressToSend(v6_address); + addresses.ipv6_.ToString())); + if (addresses.dnat_ipv6_.IsInitialized()) { + ENVOY_BUG( + addresses.dnat_ipv6_.host().address_family() == quiche::IpAddressFamily::IP_V6, + absl::StrCat("Configured IPv6 server's preferred DNAT address isn't a v6 address:", + addresses.dnat_ipv6_.ToString())); + quic_config_.SetIPv6AlternateServerAddressForDNat(addresses.ipv6_, addresses.dnat_ipv6_); + } else { + quic_config_.SetIPv6AlternateServerAddressToSend(addresses.ipv6_); + } } } diff --git a/source/common/quic/envoy_quic_server_preferred_address_config_factory.h b/source/common/quic/envoy_quic_server_preferred_address_config_factory.h index 93a8d3401f85..53df4afa4d72 100644 --- a/source/common/quic/envoy_quic_server_preferred_address_config_factory.h +++ b/source/common/quic/envoy_quic_server_preferred_address_config_factory.h @@ -15,6 +15,20 @@ class EnvoyQuicServerPreferredAddressConfig { public: virtual ~EnvoyQuicServerPreferredAddressConfig() = default; + struct Addresses { + // Addresses that client is requested to use. + quic::QuicSocketAddress ipv4_; + quic::QuicSocketAddress ipv6_; + + // If destination NAT is applied between the client and Envoy, the addresses that + // Envoy will see for client traffic to the server preferred address. If this is not + // set, Envoy will expect to receive server preferred address traffic on the above addresses. + // + // A DNAT address will be ignored if the corresponding SPA address is not set. + quic::QuicSocketAddress dnat_ipv4_; + quic::QuicSocketAddress dnat_ipv6_; + }; + /** * Called during config loading. * @param local_address the configured default listening address. @@ -22,7 +36,7 @@ class EnvoyQuicServerPreferredAddressConfig { * the entire life time of the QUIC listener. An uninitialized address value means no preferred * address for that address family. */ - virtual std::pair + virtual Addresses getServerPreferredAddresses(const Network::Address::InstanceConstSharedPtr& local_address) PURE; }; diff --git a/source/extensions/quic/server_preferred_address/BUILD b/source/extensions/quic/server_preferred_address/BUILD index 9f2a46f28320..3e42f9ac7f9d 100644 --- a/source/extensions/quic/server_preferred_address/BUILD +++ b/source/extensions/quic/server_preferred_address/BUILD @@ -23,6 +23,7 @@ envoy_cc_library( deps = [ "//envoy/registry", "//source/common/quic:envoy_quic_server_preferred_address_config_factory_interface", + "//source/common/quic:envoy_quic_utils_lib", "@envoy_api//envoy/extensions/quic/server_preferred_address/v3:pkg_cc_proto", ], alwayslink = LEGACY_ALWAYSLINK, diff --git a/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc b/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc index ff34cf2c9eae..ef0a02492bd2 100644 --- a/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc +++ b/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc @@ -1,13 +1,75 @@ #include "source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.h" +#include "source/common/network/utility.h" +#include "source/common/quic/envoy_quic_utils.h" + namespace Envoy { namespace Quic { -std::pair +namespace { + +quic::QuicSocketAddress +ipOrAddressToAddress(const FixedServerPreferredAddressConfig::QuicSocketOrIpAddress& address, + int32_t port) { + return absl::visit( + [&](const auto& arg) -> quic::QuicSocketAddress { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return arg; + } else if constexpr (std::is_same_v) { + return quic::QuicSocketAddress(arg, port); + } else { + IS_ENVOY_BUG(fmt::format("Unhandled type in variant visitor: {}", typeof(arg))); + return {}; + } + }, + address); +} + +quic::QuicIpAddress parseIp(const std::string& addr, absl::string_view version, + const Protobuf::Message& message) { + quic::QuicIpAddress ip; + if (!ip.FromString(addr)) { + ProtoExceptionUtil::throwProtoValidationException( + absl::StrCat("bad ", version, " server preferred address: ", addr), message); + } + return ip; +} + +quic::QuicSocketAddress parseSocketAddress(const envoy::config::core::v3::SocketAddress& addr, + Network::Address::IpVersion version, + absl::string_view version_str, + const Protobuf::Message& message) { + // There's no utility to convert from a `SocketAddress`, so wrap it in an `Address` to make use of + // existing helpers. + envoy::config::core::v3::Address outer; + *outer.mutable_socket_address() = addr; + auto envoy_addr = Network::Utility::protobufAddressToAddress(outer); + if (envoy_addr == nullptr) { + ProtoExceptionUtil::throwProtoValidationException( + absl::StrCat("bad ", version_str, " server preferred address: ", addr), message); + } + if (envoy_addr->ip() == nullptr || envoy_addr->ip()->version() != version) { + ProtoExceptionUtil::throwProtoValidationException( + absl::StrCat("wrong address type for ", version_str, " server preferred address: ", addr), + message); + } + + return envoyIpAddressToQuicSocketAddress(envoy_addr->ip()); +} + +} // namespace + +EnvoyQuicServerPreferredAddressConfig::Addresses FixedServerPreferredAddressConfig::getServerPreferredAddresses( const Network::Address::InstanceConstSharedPtr& local_address) { int32_t port = local_address->ip()->port(); - return {quic::QuicSocketAddress(ip_v4_, port), quic::QuicSocketAddress(ip_v6_, port)}; + Addresses addresses; + addresses.ipv4_ = ipOrAddressToAddress(ip_v4_, port); + addresses.ipv6_ = ipOrAddressToAddress(ip_v6_, port); + addresses.dnat_ipv4_ = quic::QuicSocketAddress(dnat_ip_v4_, port); + addresses.dnat_ipv6_ = quic::QuicSocketAddress(dnat_ip_v6_, port); + return addresses; } Quic::EnvoyQuicServerPreferredAddressConfigPtr @@ -18,20 +80,47 @@ FixedServerPreferredAddressConfigFactory::createServerPreferredAddressConfig( MessageUtil::downcastAndValidate(message, validation_visitor); - quic::QuicIpAddress ip_v4, ip_v6; - if (config.has_ipv4_address()) { - if (!ip_v4.FromString(config.ipv4_address())) { - ProtoExceptionUtil::throwProtoValidationException( - absl::StrCat("bad v4 server preferred address: ", config.ipv4_address()), message); - } + FixedServerPreferredAddressConfig::QuicSocketOrIpAddress ip_v4, ip_v6; + switch (config.ipv4_type_case()) { + case envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig:: + kIpv4Address: + ip_v4 = parseIp(config.ipv4_address(), "v4", message); + break; + case envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig:: + kIpv4AddressAndPort: + ip_v4 = parseSocketAddress(config.ipv4_address_and_port(), Network::Address::IpVersion::v4, + "v4", message); + break; + case envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig:: + IPV4_TYPE_NOT_SET: + break; } - if (config.has_ipv6_address()) { - if (!ip_v6.FromString(config.ipv6_address())) { - ProtoExceptionUtil::throwProtoValidationException( - absl::StrCat("bad v6 server preferred address: ", config.ipv6_address()), message); - } + + switch (config.ipv6_type_case()) { + case envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig:: + kIpv6Address: + ip_v6 = parseIp(config.ipv6_address(), "v6", message); + break; + case envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig:: + kIpv6AddressAndPort: + ip_v6 = parseSocketAddress(config.ipv6_address_and_port(), Network::Address::IpVersion::v6, + "v6", message); + break; + case envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig:: + IPV6_TYPE_NOT_SET: + break; } - return std::make_unique(ip_v4, ip_v6); + + quic::QuicIpAddress dnat_v4, dnat_v6; + if (config.has_ipv4_dnat_address()) { + dnat_v4 = parseIp(config.ipv4_dnat_address(), "dnat v4", message); + } + + if (config.has_ipv6_dnat_address()) { + dnat_v6 = parseIp(config.ipv6_dnat_address(), "dnat v6", message); + } + + return std::make_unique(ip_v4, ip_v6, dnat_v4, dnat_v6); } REGISTER_FACTORY(FixedServerPreferredAddressConfigFactory, diff --git a/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.h b/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.h index c9e70f30e8a5..292610428fc5 100644 --- a/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.h +++ b/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.h @@ -11,16 +11,22 @@ namespace Quic { class FixedServerPreferredAddressConfig : public Quic::EnvoyQuicServerPreferredAddressConfig { public: - FixedServerPreferredAddressConfig(const quic::QuicIpAddress& ipv4, - const quic::QuicIpAddress& ipv6) - : ip_v4_(ipv4), ip_v6_(ipv6) {} + using QuicSocketOrIpAddress = absl::variant; - std::pair getServerPreferredAddresses( + FixedServerPreferredAddressConfig(const QuicSocketOrIpAddress& ipv4, + const QuicSocketOrIpAddress& ipv6, + const quic::QuicIpAddress ipv4_dnat, + const quic::QuicIpAddress ipv6_dnat) + : ip_v4_(ipv4), ip_v6_(ipv6), dnat_ip_v4_(ipv4_dnat), dnat_ip_v6_(ipv6_dnat) {} + + Addresses getServerPreferredAddresses( const Network::Address::InstanceConstSharedPtr& local_address) override; private: - const quic::QuicIpAddress ip_v4_; - const quic::QuicIpAddress ip_v6_; + const QuicSocketOrIpAddress ip_v4_; + const QuicSocketOrIpAddress ip_v6_; + const quic::QuicIpAddress dnat_ip_v4_; + const quic::QuicIpAddress dnat_ip_v6_; }; class FixedServerPreferredAddressConfigFactory diff --git a/test/integration/filters/test_socket_interface.cc b/test/integration/filters/test_socket_interface.cc index 02f2618ca24a..feec0dd4b585 100644 --- a/test/integration/filters/test_socket_interface.cc +++ b/test/integration/filters/test_socket_interface.cc @@ -13,24 +13,44 @@ namespace Envoy { namespace Network { +Api::IoCallUint64Result TestIoSocketHandle::recvmsg(Buffer::RawSlice* slices, + const uint64_t num_slice, uint32_t self_port, + RecvMsgOutput& output) { + + auto result = Test::IoSocketHandlePlatformImpl::recvmsg(slices, num_slice, self_port, output); + if (read_override_) { + read_override_(output); + } + + return result; +} + Api::IoCallUint64Result TestIoSocketHandle::sendmsg(const Buffer::RawSlice* slices, uint64_t num_slice, int flags, const Address::Ip* self_ip, const Address::Instance& peer_address) { + Address::InstanceConstSharedPtr dnat_peer_address; + if (write_override_) { peer_address_override_ = peer_address; - auto result = write_override_(this, slices, num_slice); + auto result = + write_override_(this, slices, num_slice, + dnat_peer_address); // `dnat_peer_address` can be changed in this call. peer_address_override_.reset(); if (result.has_value()) { return std::move(result).value(); } } - return Test::IoSocketHandlePlatformImpl::sendmsg(slices, num_slice, flags, self_ip, peer_address); + return Test::IoSocketHandlePlatformImpl::sendmsg( + slices, num_slice, flags, self_ip, + (dnat_peer_address != nullptr) ? *dnat_peer_address : peer_address); } Api::IoCallUint64Result TestIoSocketHandle::writev(const Buffer::RawSlice* slices, uint64_t num_slice) { + Address::InstanceConstSharedPtr dnat_peer_address; if (write_override_) { - auto result = write_override_(this, slices, num_slice); + auto result = write_override_(this, slices, num_slice, dnat_peer_address); + ENVOY_BUG(dnat_peer_address == nullptr, "Only works for sendmsg, not writev"); if (result.has_value()) { return std::move(result).value(); } @@ -44,8 +64,8 @@ IoHandlePtr TestIoSocketHandle::accept(struct sockaddr* addr, socklen_t* addrlen return nullptr; } - return std::make_unique(write_override_, result.return_value_, socket_v6only_, - domain_); + return std::make_unique(connect_override_, write_override_, read_override_, + result.return_value_, socket_v6only_, domain_); } IoHandlePtr TestIoSocketHandle::duplicate() { @@ -54,23 +74,24 @@ IoHandlePtr TestIoSocketHandle::duplicate() { throw EnvoyException(fmt::format("duplicate failed for '{}': ({}) {}", fd_, result.errno_, errorDetails(result.errno_))); } - return std::make_unique(write_override_, result.return_value_, socket_v6only_, - domain_); + return std::make_unique(connect_override_, write_override_, read_override_, + result.return_value_, socket_v6only_, domain_); } Api::SysCallIntResult TestIoSocketHandle::connect(Address::InstanceConstSharedPtr address) { - if (write_override_) { - auto result = write_override_(this, nullptr, 0); - if (result.has_value()) { + if (connect_override_) { + auto result = connect_override_(this); + if (result.has_value()) return Api::SysCallIntResult{-1, EINPROGRESS}; - } } + return Test::IoSocketHandlePlatformImpl::connect(address); } IoHandlePtr TestSocketInterface::makeSocket(int socket_fd, bool socket_v6only, absl::optional domain) const { - return std::make_unique(write_override_proc_, socket_fd, socket_v6only, + return std::make_unique(connect_override_proc_, write_override_proc_, + read_override_proc_, socket_fd, socket_v6only, domain); } diff --git a/test/integration/filters/test_socket_interface.h b/test/integration/filters/test_socket_interface.h index 850904a7ebd2..df6d1cf56cae 100644 --- a/test/integration/filters/test_socket_interface.h +++ b/test/integration/filters/test_socket_interface.h @@ -22,15 +22,21 @@ namespace Network { class TestIoSocketHandle : public Test::IoSocketHandlePlatformImpl { public: - using WriteOverrideType = absl::optional(TestIoSocketHandle* io_handle, - const Buffer::RawSlice* slices, - uint64_t num_slice); + using WriteOverrideType = absl::optional( + TestIoSocketHandle* io_handle, const Buffer::RawSlice* slices, uint64_t num_slice, + Address::InstanceConstSharedPtr& peer_address_override_out); using WriteOverrideProc = std::function; - - TestIoSocketHandle(WriteOverrideProc write_override_proc, os_fd_t fd = INVALID_SOCKET, - bool socket_v6only = false, absl::optional domain = absl::nullopt) + using ReadOverrideProc = std::function; + using ConnectOverrideProc = + std::function(TestIoSocketHandle* io_handle)>; + + TestIoSocketHandle(ConnectOverrideProc connect_override_proc, + WriteOverrideProc write_override_proc, ReadOverrideProc read_override_proc, + os_fd_t fd = INVALID_SOCKET, bool socket_v6only = false, + absl::optional domain = absl::nullopt) : Test::IoSocketHandlePlatformImpl(fd, socket_v6only, domain), - write_override_(write_override_proc) { + connect_override_(connect_override_proc), write_override_(write_override_proc), + read_override_(read_override_proc) { int type; socklen_t length = sizeof(int); EXPECT_EQ(0, getOption(SOL_SOCKET, SO_TYPE, &type, &length).return_value_); @@ -73,11 +79,15 @@ class TestIoSocketHandle : public Test::IoSocketHandlePlatformImpl { Api::IoCallUint64Result sendmsg(const Buffer::RawSlice* slices, uint64_t num_slice, int flags, const Address::Ip* self_ip, const Address::Instance& peer_address) override; + Api::IoCallUint64Result recvmsg(Buffer::RawSlice* slices, const uint64_t num_slice, + uint32_t self_port, RecvMsgOutput& output) override; IoHandlePtr duplicate() override; OptRef peer_address_override_; + const ConnectOverrideProc connect_override_; const WriteOverrideProc write_override_; + const ReadOverrideProc read_override_; absl::Mutex mutex_; Event::Dispatcher* dispatcher_ ABSL_GUARDED_BY(mutex_) = nullptr; Socket::Type socket_type_; @@ -102,14 +112,19 @@ class TestSocketInterface : public SocketInterfaceImpl { * write methods. Returning a Api::IoCallUint64Result from callback skips * the write methods with the returned result value. */ - TestSocketInterface(TestIoSocketHandle::WriteOverrideProc write) : write_override_proc_(write) {} + TestSocketInterface(TestIoSocketHandle::ConnectOverrideProc connect, + TestIoSocketHandle::WriteOverrideProc write, + TestIoSocketHandle::ReadOverrideProc read) + : connect_override_proc_(connect), write_override_proc_(write), read_override_proc_(read) {} private: // SocketInterfaceImpl IoHandlePtr makeSocket(int socket_fd, bool socket_v6only, absl::optional domain) const override; + const TestIoSocketHandle::ConnectOverrideProc connect_override_proc_; const TestIoSocketHandle::WriteOverrideProc write_override_proc_; + const TestIoSocketHandle::ReadOverrideProc read_override_proc_; }; } // namespace Network diff --git a/test/integration/quic_http_integration_test.cc b/test/integration/quic_http_integration_test.cc index e18042946688..60abe8deb69e 100644 --- a/test/integration/quic_http_integration_test.cc +++ b/test/integration/quic_http_integration_test.cc @@ -1935,6 +1935,83 @@ TEST_P(QuicHttpIntegrationTest, UsesPreferredAddress) { } } +TEST_P(QuicHttpIntegrationTest, UsesPreferredAddressDNAT) { + autonomous_upstream_ = true; + config_helper_.addConfigModifier( + [=, this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { + auto* listen_address = bootstrap.mutable_static_resources() + ->mutable_listeners(0) + ->mutable_address() + ->mutable_socket_address(); + // Change listening address to Any. + listen_address->set_address(Network::Test::getAnyAddressString(version_)); + auto* preferred_address_config = bootstrap.mutable_static_resources() + ->mutable_listeners(0) + ->mutable_udp_listener_config() + ->mutable_quic_options() + ->mutable_server_preferred_address_config(); + + // Configure a loopback interface as the server's preferred address. + preferred_address_config->set_name("quic.server_preferred_address.fixed"); + envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig + server_preferred_address; + server_preferred_address.mutable_ipv4_address_and_port()->set_address("1.2.3.4"); + server_preferred_address.mutable_ipv4_address_and_port()->set_port_value(12345); + server_preferred_address.set_ipv4_dnat_address("127.0.0.2"); + preferred_address_config->mutable_typed_config()->PackFrom(server_preferred_address); + + // Configure a test listener filter which is incompatible with any server preferred + // addresses but with any matcher, which effectively disables the filter. + auto* listener_filter = + bootstrap.mutable_static_resources()->mutable_listeners(0)->add_listener_filters(); + listener_filter->set_name("dumb_filter"); + auto configuration = test::integration::filters::TestQuicListenerFilterConfig(); + configuration.set_added_value("foo"); + configuration.set_allow_server_migration(false); + configuration.set_allow_client_migration(false); + listener_filter->mutable_typed_config()->PackFrom(configuration); + listener_filter->mutable_filter_disabled()->set_any_match(true); + }); + + initialize(); + auto listener_port = lookupPort("http"); + + // Setup DNAT for 0.0.0.0:12345-->127.0.0.2:listener_port + SocketInterfaceSwap socket_swap(Network::Socket::Type::Datagram); + socket_swap.write_matcher_->setDnat( + Network::Utility::parseInternetAddress("1.2.3.4", 12345), + Network::Utility::parseInternetAddress("127.0.0.2", listener_port)); + + codec_client_ = makeHttpConnection(makeClientConnection(listener_port)); + EnvoyQuicClientSession* quic_session = + static_cast(codec_client_->connection()); + EXPECT_EQ(Network::Test::getLoopbackAddressString(version_), + quic_connection_->peer_address().host().ToString()); + ASSERT_TRUE((version_ == Network::Address::IpVersion::v4 && + quic_session->config()->HasReceivedIPv4AlternateServerAddress()) || + (version_ == Network::Address::IpVersion::v6 && + quic_session->config()->HasReceivedIPv6AlternateServerAddress())); + ASSERT_TRUE(quic_connection_->waitForHandshakeDone()); + EXPECT_TRUE(quic_connection_->IsValidatingServerPreferredAddress()); + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "GET"}, + {":path", "/test/long/url"}, + {":authority", "sni.lyft.com"}, + {":scheme", "http"}, + {AutonomousStream::RESPONSE_SIZE_BYTES, std::to_string(1024 * 1024)}}; + IntegrationStreamDecoderPtr response = + codec_client_->makeHeaderOnlyRequest(default_request_headers_); + EXPECT_TRUE(response->waitForEndStream()); + ASSERT_TRUE(response->complete()); + + if (version_ == Network::Address::IpVersion::v4) { + // Most v6 platform doesn't support two loopback interfaces. + EXPECT_EQ("1.2.3.4", quic_connection_->peer_address().host().ToString()); + test_server_->waitForCounterGe( + "listener.0.0.0.0_0.quic.connection.num_packets_rx_on_preferred_address", 2u); + } +} + TEST_P(QuicHttpIntegrationTest, PreferredAddressRuntimeFlag) { autonomous_upstream_ = true; config_helper_.addRuntimeOverride( diff --git a/test/integration/socket_interface_swap.cc b/test/integration/socket_interface_swap.cc index b441ebd09a16..9ea29fa7ad3b 100644 --- a/test/integration/socket_interface_swap.cc +++ b/test/integration/socket_interface_swap.cc @@ -7,23 +7,27 @@ SocketInterfaceSwap::SocketInterfaceSwap(Network::Socket::Type socket_type) Envoy::Network::SocketInterfaceSingleton::clear(); test_socket_interface_loader_ = std::make_unique( std::make_unique( + [write_matcher = write_matcher_](Envoy::Network::TestIoSocketHandle* io_handle) + -> absl::optional { + Api::IoErrorPtr error_override = write_matcher->returnConnectOverride(io_handle); + if (error_override) { + return Api::IoCallUint64Result(0, std::move(error_override)); + } + return absl::nullopt; + }, [write_matcher = write_matcher_]( - Envoy::Network::TestIoSocketHandle* io_handle, const Buffer::RawSlice* slices, - uint64_t size) -> absl::optional { - // TODO(yanavlasov): refactor into separate method after CVE is public. - if (slices == nullptr && size == 0) { - // This is connect override check - Api::IoErrorPtr error_override = write_matcher->returnConnectOverride(io_handle); - if (error_override) { - return Api::IoCallUint64Result(0, std::move(error_override)); - } - } else { - Api::IoErrorPtr error_override = write_matcher->returnOverride(io_handle); - if (error_override) { - return Api::IoCallUint64Result(0, std::move(error_override)); - } + Envoy::Network::TestIoSocketHandle* io_handle, const Buffer::RawSlice*, uint64_t, + Network::Address::InstanceConstSharedPtr& peer_address_override_out) + -> absl::optional { + Api::IoErrorPtr error_override = + write_matcher->returnOverride(io_handle, peer_address_override_out); + if (error_override) { + return Api::IoCallUint64Result(0, std::move(error_override)); } return absl::nullopt; + }, + [write_matcher = write_matcher_](Network::IoHandle::RecvMsgOutput& output) { + write_matcher->readOverride(output); })); } diff --git a/test/integration/socket_interface_swap.h b/test/integration/socket_interface_swap.h index 87127e4c71c2..288db6561eb4 100644 --- a/test/integration/socket_interface_swap.h +++ b/test/integration/socket_interface_swap.h @@ -16,7 +16,9 @@ class SocketInterfaceSwap { struct IoHandleMatcher { explicit IoHandleMatcher(Network::Socket::Type type) : socket_type_(type) {} - Api::IoErrorPtr returnOverride(Envoy::Network::TestIoSocketHandle* io_handle) { + Api::IoErrorPtr + returnOverride(Envoy::Network::TestIoSocketHandle* io_handle, + Network::Address::InstanceConstSharedPtr& peer_address_override_out) { absl::MutexLock lock(&mutex_); if (socket_type_ == io_handle->getSocketType() && error_ && (io_handle->localAddress()->ip()->port() == src_port_ || @@ -28,6 +30,11 @@ class SocketInterfaceSwap { ? Envoy::Network::IoSocketError::getIoSocketEagainError() : Envoy::Network::IoSocketError::create(error_->getSystemErrorCode()); } + + if (orig_dnat_address_ && *orig_dnat_address_ == *io_handle->peerAddress()) { + peer_address_override_out = translated_dnat_address_; + } + return Api::IoError::none(); } @@ -41,6 +48,16 @@ class SocketInterfaceSwap { return Api::IoError::none(); } + void readOverride(Network::IoHandle::RecvMsgOutput& output) { + absl::MutexLock lock(&mutex_); + for (auto& pkt : output.msg_) { + // Reverse DNAT when receiving packets. + if (pkt.peer_address_ && *pkt.peer_address_ == *translated_dnat_address_) { + pkt.peer_address_ = orig_dnat_address_; + } + } + } + // Source port to match. The port specified should be associated with a listener. void setSourcePort(uint32_t port) { absl::WriterMutexLock lock(&mutex_); @@ -72,6 +89,13 @@ class SocketInterfaceSwap { block_connect_ = block; } + void setDnat(Network::Address::InstanceConstSharedPtr orig_address, + Network::Address::InstanceConstSharedPtr translated_address) { + absl::WriterMutexLock lock(&mutex_); + orig_dnat_address_ = orig_address; + translated_dnat_address_ = translated_address; + } + void setResumeWrites(); private: @@ -82,6 +106,8 @@ class SocketInterfaceSwap { Network::TestIoSocketHandle* matched_iohandle_{}; Network::Socket::Type socket_type_; bool block_connect_ ABSL_GUARDED_BY(mutex_) = false; + Network::Address::InstanceConstSharedPtr orig_dnat_address_ ABSL_GUARDED_BY(mutex_); + Network::Address::InstanceConstSharedPtr translated_dnat_address_ ABSL_GUARDED_BY(mutex_); }; explicit SocketInterfaceSwap(Network::Socket::Type socket_type); From 78abeb1f0478f726b62b4888e4a6f4c8fc1b493f Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Wed, 24 Apr 2024 16:13:03 -0700 Subject: [PATCH 02/22] fixes Signed-off-by: Greg Greenway --- .../v3/fixed_server_preferred_address_config.proto | 8 ++++---- test/integration/quic_http_integration_test.cc | 3 +++ tools/spelling/spelling_dictionary.txt | 2 ++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto b/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto index 8286052c836e..6cf641824d6e 100644 --- a/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto +++ b/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto @@ -18,6 +18,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#extension: envoy.quic.server_preferred_address.fixed] // Configuration for FixedServerPreferredAddressConfig. +// [#next-free-field: 7] message FixedServerPreferredAddressConfig { // [#comment:TODO(danzh2010): discuss with API shepherds before removing WiP status.] @@ -29,9 +30,8 @@ message FixedServerPreferredAddressConfig { // If not specified, none will be configured. string ipv4_address = 1; - // The IPv4 address to advertise to clients for Server Preferred Address. - envoy.config.core.v3.SocketAddress ipv4_address_and_port = 3; + config.core.v3.SocketAddress ipv4_address_and_port = 3; } oneof ipv4_dnat_type { @@ -51,10 +51,10 @@ message FixedServerPreferredAddressConfig { // If not specified, none will be configured. string ipv6_address = 2; - // The IPv6 address to advertise to clients for Server Preferred Address. - envoy.config.core.v3.SocketAddress ipv6_address_and_port = 4; + config.core.v3.SocketAddress ipv6_address_and_port = 4; } + oneof ipv6_dnat_type { // If there is a DNAT between the client and Envoy, the address that Envoy will observe // server preferred address IPv6 packets being sent to. If this is not specified, it is assumed diff --git a/test/integration/quic_http_integration_test.cc b/test/integration/quic_http_integration_test.cc index 60abe8deb69e..94308d05594c 100644 --- a/test/integration/quic_http_integration_test.cc +++ b/test/integration/quic_http_integration_test.cc @@ -1957,7 +1957,10 @@ TEST_P(QuicHttpIntegrationTest, UsesPreferredAddressDNAT) { server_preferred_address; server_preferred_address.mutable_ipv4_address_and_port()->set_address("1.2.3.4"); server_preferred_address.mutable_ipv4_address_and_port()->set_port_value(12345); + server_preferred_address.mutable_ipv6_address_and_port()->set_address("::1"); + server_preferred_address.mutable_ipv6_address_and_port()->set_port_value(12345); server_preferred_address.set_ipv4_dnat_address("127.0.0.2"); + server_preferred_address.set_ipv6_dnat_address("::2"); preferred_address_config->mutable_typed_config()->PackFrom(server_preferred_address); // Configure a test listener filter which is incompatible with any server preferred diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 11d36cb233ce..9cceb9943183 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -1503,3 +1503,5 @@ DLB PCIE EDNS CNAME +NAT +DNAT From bb84625b22a74f206059979286cf4398f6e451a1 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Wed, 24 Apr 2024 17:00:31 -0700 Subject: [PATCH 03/22] fix Signed-off-by: Greg Greenway --- test/integration/socket_interface_swap.h | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/integration/socket_interface_swap.h b/test/integration/socket_interface_swap.h index 288db6561eb4..ad4669b4f59c 100644 --- a/test/integration/socket_interface_swap.h +++ b/test/integration/socket_interface_swap.h @@ -31,7 +31,7 @@ class SocketInterfaceSwap { : Envoy::Network::IoSocketError::create(error_->getSystemErrorCode()); } - if (orig_dnat_address_ && *orig_dnat_address_ == *io_handle->peerAddress()) { + if (orig_dnat_address_ != nullptr && *orig_dnat_address_ == *io_handle->peerAddress()) { peer_address_override_out = translated_dnat_address_; } @@ -50,10 +50,12 @@ class SocketInterfaceSwap { void readOverride(Network::IoHandle::RecvMsgOutput& output) { absl::MutexLock lock(&mutex_); - for (auto& pkt : output.msg_) { - // Reverse DNAT when receiving packets. - if (pkt.peer_address_ && *pkt.peer_address_ == *translated_dnat_address_) { - pkt.peer_address_ = orig_dnat_address_; + if (translated_dnat_address_ != nullptr) { + for (auto& pkt : output.msg_) { + // Reverse DNAT when receiving packets. + if (pkt.peer_address_ != nullptr && *pkt.peer_address_ == *translated_dnat_address_) { + pkt.peer_address_ = orig_dnat_address_; + } } } } From e25ccb4519c76e7c46447a6b2507c04e9ffa72fc Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Wed, 24 Apr 2024 17:10:29 -0700 Subject: [PATCH 04/22] add assert to test code Signed-off-by: Greg Greenway --- test/integration/socket_interface_swap.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/integration/socket_interface_swap.h b/test/integration/socket_interface_swap.h index ad4669b4f59c..57e82087de8f 100644 --- a/test/integration/socket_interface_swap.h +++ b/test/integration/socket_interface_swap.h @@ -32,6 +32,7 @@ class SocketInterfaceSwap { } if (orig_dnat_address_ != nullptr && *orig_dnat_address_ == *io_handle->peerAddress()) { + ASSERT(translated_dnat_address_ != nullptr); peer_address_override_out = translated_dnat_address_; } @@ -54,6 +55,7 @@ class SocketInterfaceSwap { for (auto& pkt : output.msg_) { // Reverse DNAT when receiving packets. if (pkt.peer_address_ != nullptr && *pkt.peer_address_ == *translated_dnat_address_) { + ASSERT(orig_dnat_address_ != nullptr); pkt.peer_address_ = orig_dnat_address_; } } From 93f21e676267618a4c8d9b1dd7339eea948b0e4f Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Wed, 24 Apr 2024 17:22:52 -0700 Subject: [PATCH 05/22] review comments Signed-off-by: Greg Greenway --- ...quic_server_preferred_address_config_factory.h | 1 + .../fixed_server_preferred_address_config.cc | 15 +++++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/source/common/quic/envoy_quic_server_preferred_address_config_factory.h b/source/common/quic/envoy_quic_server_preferred_address_config_factory.h index 53df4afa4d72..3ec363968e08 100644 --- a/source/common/quic/envoy_quic_server_preferred_address_config_factory.h +++ b/source/common/quic/envoy_quic_server_preferred_address_config_factory.h @@ -15,6 +15,7 @@ class EnvoyQuicServerPreferredAddressConfig { public: virtual ~EnvoyQuicServerPreferredAddressConfig() = default; + // The set of addresses used to configure the server preferred addresses. struct Addresses { // Addresses that client is requested to use. quic::QuicSocketAddress ipv4_; diff --git a/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc b/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc index ef0a02492bd2..4ec7c29d46cd 100644 --- a/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc +++ b/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc @@ -14,24 +14,27 @@ ipOrAddressToAddress(const FixedServerPreferredAddressConfig::QuicSocketOrIpAddr return absl::visit( [&](const auto& arg) -> quic::QuicSocketAddress { using T = std::decay_t; + if constexpr (std::is_same_v) { return arg; - } else if constexpr (std::is_same_v) { + } + + if constexpr (std::is_same_v) { return quic::QuicSocketAddress(arg, port); - } else { - IS_ENVOY_BUG(fmt::format("Unhandled type in variant visitor: {}", typeof(arg))); - return {}; } + + IS_ENVOY_BUG(fmt::format("Unhandled type in variant visitor: {}", address.index())); + return {}; }, address); } -quic::QuicIpAddress parseIp(const std::string& addr, absl::string_view version, +quic::QuicIpAddress parseIp(const std::string& addr, absl::string_view address_family, const Protobuf::Message& message) { quic::QuicIpAddress ip; if (!ip.FromString(addr)) { ProtoExceptionUtil::throwProtoValidationException( - absl::StrCat("bad ", version, " server preferred address: ", addr), message); + absl::StrCat("bad ", address_family, " server preferred address: ", addr), message); } return ip; } From b8a812b19aab2666439fa08467471c420ffa1e62 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Thu, 25 Apr 2024 11:37:43 -0700 Subject: [PATCH 06/22] add coverage Signed-off-by: Greg Greenway --- .../fixed_server_preferred_address_config.cc | 6 +- .../quic/server_preferred_address/BUILD | 24 ++++++ .../fixed_server_preferred_address_test.cc | 75 +++++++++++++++++++ 3 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 test/extensions/quic/server_preferred_address/BUILD create mode 100644 test/extensions/quic/server_preferred_address/fixed_server_preferred_address_test.cc diff --git a/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc b/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc index 4ec7c29d46cd..6cfdf0c9f290 100644 --- a/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc +++ b/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc @@ -48,10 +48,8 @@ quic::QuicSocketAddress parseSocketAddress(const envoy::config::core::v3::Socket envoy::config::core::v3::Address outer; *outer.mutable_socket_address() = addr; auto envoy_addr = Network::Utility::protobufAddressToAddress(outer); - if (envoy_addr == nullptr) { - ProtoExceptionUtil::throwProtoValidationException( - absl::StrCat("bad ", version_str, " server preferred address: ", addr), message); - } + ASSERT(envoy_addr != nullptr, + "Network::Utility::protobufAddressToAddress throws on failure so this can't be nullptr"); if (envoy_addr->ip() == nullptr || envoy_addr->ip()->version() != version) { ProtoExceptionUtil::throwProtoValidationException( absl::StrCat("wrong address type for ", version_str, " server preferred address: ", addr), diff --git a/test/extensions/quic/server_preferred_address/BUILD b/test/extensions/quic/server_preferred_address/BUILD new file mode 100644 index 000000000000..767bd6a11b21 --- /dev/null +++ b/test/extensions/quic/server_preferred_address/BUILD @@ -0,0 +1,24 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test_library", + "envoy_package", +) +load( + "//test/extensions:extensions_build_system.bzl", + "envoy_extension_cc_test", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_extension_cc_test( + name = "fixed_server_preferred_address_test", + srcs = ["fixed_server_preferred_address_test.cc"], + extension_names = ["envoy.quic.server_preferred_address.fixed"], + tags = ["nofips"], + deps = [ + "//source/extensions/quic/server_preferred_address:fixed_server_preferred_address_config_lib", + "//test/mocks/protobuf:protobuf_mocks", + ], +) diff --git a/test/extensions/quic/server_preferred_address/fixed_server_preferred_address_test.cc b/test/extensions/quic/server_preferred_address/fixed_server_preferred_address_test.cc new file mode 100644 index 000000000000..c6b95d0adda0 --- /dev/null +++ b/test/extensions/quic/server_preferred_address/fixed_server_preferred_address_test.cc @@ -0,0 +1,75 @@ +#include "source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.h" + +#include "source/common/network/utility.h" +#include "test/test_common/utility.h" +#include "test/mocks/protobuf/mocks.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Quic { + +class FixedServerPreferredAddressConfigTest : public ::testing::Test { +public: + FixedServerPreferredAddressConfigFactory factory_; + testing::NiceMock visitor_; +}; + +TEST_F(FixedServerPreferredAddressConfigTest, Validation) { + { + // Bad address_and_port. + envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig cfg; + cfg.mutable_ipv4_address_and_port()->set_address("not an address"); + cfg.mutable_ipv4_address_and_port()->set_port_value(1); + EXPECT_THROW_WITH_REGEX(factory_.createServerPreferredAddressConfig(cfg, visitor_, {}), + EnvoyException, ".*malformed IP address: not an address.*"); + } + { + // Bad address. + envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig cfg; + cfg.set_ipv4_address("not an address"); + EXPECT_THROW_WITH_REGEX(factory_.createServerPreferredAddressConfig(cfg, visitor_, {}), + EnvoyException, ".*bad v4 server preferred address: not an address.*"); + } + { + // v6 address in v4 field. + envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig cfg; + cfg.mutable_ipv4_address_and_port()->set_address("::1"); + cfg.mutable_ipv4_address_and_port()->set_port_value(1); + EXPECT_THROW_WITH_REGEX(factory_.createServerPreferredAddressConfig(cfg, visitor_, {}), + EnvoyException, + ".*wrong address type for v4 server preferred address.*"); + } + { + // v4 address in v6 field. + envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig cfg; + cfg.mutable_ipv6_address_and_port()->set_address("127.0.0.1"); + cfg.mutable_ipv6_address_and_port()->set_port_value(1); + EXPECT_THROW_WITH_REGEX(factory_.createServerPreferredAddressConfig(cfg, visitor_, {}), + EnvoyException, + ".*wrong address type for v6 server preferred address.*"); + } +} + +TEST_F(FixedServerPreferredAddressConfigTest, AddressGetsCombinedWithPort) { + envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig cfg; + cfg.set_ipv4_address("1.2.3.4"); + auto obj = factory_.createServerPreferredAddressConfig(cfg, visitor_, {}); + auto addresses = obj->getServerPreferredAddresses( + Network::Utility::parseInternetAddressNoThrow("127.0.0.1", 1234)); + EXPECT_EQ(addresses.ipv4_.ToString(), "1.2.3.4:1234"); +} + +TEST_F(FixedServerPreferredAddressConfigTest, AddressAndPortIgnoresPort) { + envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig cfg; + cfg.mutable_ipv4_address_and_port()->set_address("1.2.3.4"); + cfg.mutable_ipv4_address_and_port()->set_port_value(5); + auto obj = factory_.createServerPreferredAddressConfig(cfg, visitor_, {}); + auto addresses = obj->getServerPreferredAddresses( + Network::Utility::parseInternetAddressNoThrow("127.0.0.1", 1234)); + EXPECT_EQ(addresses.ipv4_.ToString(), "1.2.3.4:5"); +} + +} // namespace Quic +} // namespace Envoy From b54cc445879e9f220b06565709b078e7da03a169 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Thu, 25 Apr 2024 12:39:02 -0700 Subject: [PATCH 07/22] fix format Signed-off-by: Greg Greenway --- test/extensions/quic/server_preferred_address/BUILD | 1 - .../fixed_server_preferred_address_test.cc | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/test/extensions/quic/server_preferred_address/BUILD b/test/extensions/quic/server_preferred_address/BUILD index 767bd6a11b21..74eff89da95a 100644 --- a/test/extensions/quic/server_preferred_address/BUILD +++ b/test/extensions/quic/server_preferred_address/BUILD @@ -1,6 +1,5 @@ load( "//bazel:envoy_build_system.bzl", - "envoy_cc_test_library", "envoy_package", ) load( diff --git a/test/extensions/quic/server_preferred_address/fixed_server_preferred_address_test.cc b/test/extensions/quic/server_preferred_address/fixed_server_preferred_address_test.cc index c6b95d0adda0..c3dcf1414784 100644 --- a/test/extensions/quic/server_preferred_address/fixed_server_preferred_address_test.cc +++ b/test/extensions/quic/server_preferred_address/fixed_server_preferred_address_test.cc @@ -1,8 +1,8 @@ +#include "source/common/network/utility.h" #include "source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.h" -#include "source/common/network/utility.h" -#include "test/test_common/utility.h" #include "test/mocks/protobuf/mocks.h" +#include "test/test_common/utility.h" #include "gmock/gmock.h" #include "gtest/gtest.h" From b21946fcfc1157bf821e639f332852b545573028 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Tue, 30 Apr 2024 09:47:21 -0700 Subject: [PATCH 08/22] update config proto based on PR feedback Signed-off-by: Greg Greenway --- ...ixed_server_preferred_address_config.proto | 46 +++++++++---------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto b/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto index 6cf641824d6e..fcca3a9755ea 100644 --- a/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto +++ b/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto @@ -24,6 +24,26 @@ message FixedServerPreferredAddressConfig { option (xds.annotations.v3.message_status).work_in_progress = true; + // Addreses for server preferred address for a single address family (IPv4 or IPv6). + message Addresses { + // The server preferred address sent to clients. + // + // Note: Envoy currently must receive all packets for a QUIC connection on the same port, so + // unless `dnat_address` is configured, the port for this address must be zero, and the listener's + // port will be used instead. + config.core.v3.SocketAddress address = 1; + + // If there is a DNAT between the client and Envoy, the address that Envoy will observe + // server preferred address packets being sent to. If this is not specified, it is assumed + // there is no DNAT and the server preferred address packets will be sent to the address advertised + // to clients for server preferred address. + // + // Note: Envoy currently must receive all packets for a QUIC connection on the same port, so the + // port for this address must be zero, and the listener's port will be used instead. + + config.core.v3.SocketAddress dnat_address = 1; + } + oneof ipv4_type { // String representation of IPv4 address, i.e. "127.0.0.2". // The listener's port will be used. @@ -31,18 +51,7 @@ message FixedServerPreferredAddressConfig { string ipv4_address = 1; // The IPv4 address to advertise to clients for Server Preferred Address. - config.core.v3.SocketAddress ipv4_address_and_port = 3; - } - - oneof ipv4_dnat_type { - // If there is a DNAT between the client and Envoy, the address that Envoy will observe - // server preferred address IPv4 packets being sent to. If this is not specified, it is assumed - // there is no DNAT and the server preferred address packets will be sent to the address advertised - // to clients for server preferred address. - // - // String representation of IPv4 address, i.e. "127.0.0.2" - // The listener's port will be used. - string ipv4_dnat_address = 5; + Addresses ipv4_config = 3; } oneof ipv6_type { @@ -52,17 +61,6 @@ message FixedServerPreferredAddressConfig { string ipv6_address = 2; // The IPv6 address to advertise to clients for Server Preferred Address. - config.core.v3.SocketAddress ipv6_address_and_port = 4; - } - - oneof ipv6_dnat_type { - // If there is a DNAT between the client and Envoy, the address that Envoy will observe - // server preferred address IPv6 packets being sent to. If this is not specified, it is assumed - // there is no DNAT and the server preferred address packets will be sent to the address advertised - // to clients for server preferred address. - // - // String representation of IPv6 address, i.e. "::1" - // The listener's port will be used. - string ipv6_dnat_address = 6; + Addresses ipv6_config = 4; } } From 6eb2c6c28457553956e4d95a5bf5cacd7e7df09d Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Tue, 30 Apr 2024 10:39:19 -0700 Subject: [PATCH 09/22] more config Signed-off-by: Greg Greenway --- .../v3/fixed_server_preferred_address_config.proto | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto b/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto index fcca3a9755ea..5cd1a67f5a01 100644 --- a/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto +++ b/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto @@ -50,17 +50,21 @@ message FixedServerPreferredAddressConfig { // If not specified, none will be configured. string ipv4_address = 1; - // The IPv4 address to advertise to clients for Server Preferred Address. - Addresses ipv4_config = 3; } + // The IPv4 address to advertise to clients for Server Preferred Address. + // This field takes precedence over `ipv4_address`. + Addresses ipv4_config = 3; + oneof ipv6_type { // String representation of IPv6 address, i.e. "::1". // The listener's port will be used. // If not specified, none will be configured. string ipv6_address = 2; - // The IPv6 address to advertise to clients for Server Preferred Address. - Addresses ipv6_config = 4; } + + // The IPv6 address to advertise to clients for Server Preferred Address. + // This field takes precedence over `ipv6_address`. + Addresses ipv6_config = 4; } From f006088e6704d37a1d41ae936d89cc6b1162dd54 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Tue, 30 Apr 2024 18:01:19 -0700 Subject: [PATCH 10/22] fixes Signed-off-by: Greg Greenway --- ...ixed_server_preferred_address_config.proto | 25 ++-- .../fixed_server_preferred_address_config.cc | 124 +++++++++--------- .../fixed_server_preferred_address_config.h | 18 ++- .../fixed_server_preferred_address_test.cc | 48 +++++-- .../integration/quic_http_integration_test.cc | 16 ++- 5 files changed, 128 insertions(+), 103 deletions(-) diff --git a/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto b/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto index 5cd1a67f5a01..45f8a281afc1 100644 --- a/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto +++ b/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto @@ -40,29 +40,22 @@ message FixedServerPreferredAddressConfig { // // Note: Envoy currently must receive all packets for a QUIC connection on the same port, so the // port for this address must be zero, and the listener's port will be used instead. - - config.core.v3.SocketAddress dnat_address = 1; + config.core.v3.SocketAddress dnat_address = 2; } - oneof ipv4_type { - // String representation of IPv4 address, i.e. "127.0.0.2". - // The listener's port will be used. - // If not specified, none will be configured. - string ipv4_address = 1; - - } + // String representation of IPv4 address, i.e. "127.0.0.2". + // The listener's port will be used. + // If not specified, none will be configured. + string ipv4_address = 1; // The IPv4 address to advertise to clients for Server Preferred Address. // This field takes precedence over `ipv4_address`. Addresses ipv4_config = 3; - oneof ipv6_type { - // String representation of IPv6 address, i.e. "::1". - // The listener's port will be used. - // If not specified, none will be configured. - string ipv6_address = 2; - - } + // String representation of IPv6 address, i.e. "::1". + // The listener's port will be used. + // If not specified, none will be configured. + string ipv6_address = 2; // The IPv6 address to advertise to clients for Server Preferred Address. // This field takes precedence over `ipv6_address`. diff --git a/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc b/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc index 6cfdf0c9f290..36d9d9b5e7a6 100644 --- a/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc +++ b/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc @@ -8,25 +8,12 @@ namespace Quic { namespace { -quic::QuicSocketAddress -ipOrAddressToAddress(const FixedServerPreferredAddressConfig::QuicSocketOrIpAddress& address, - int32_t port) { - return absl::visit( - [&](const auto& arg) -> quic::QuicSocketAddress { - using T = std::decay_t; - - if constexpr (std::is_same_v) { - return arg; - } - - if constexpr (std::is_same_v) { - return quic::QuicSocketAddress(arg, port); - } - - IS_ENVOY_BUG(fmt::format("Unhandled type in variant visitor: {}", address.index())); - return {}; - }, - address); +quic::QuicSocketAddress ipOrAddressToAddress(const quic::QuicSocketAddress& address, int32_t port) { + if (address.port() == 0) { + return quic::QuicSocketAddress(address.host(), port); + } + + return address; } quic::QuicIpAddress parseIp(const std::string& addr, absl::string_view address_family, @@ -59,6 +46,51 @@ quic::QuicSocketAddress parseSocketAddress(const envoy::config::core::v3::Socket return envoyIpAddressToQuicSocketAddress(envoy_addr->ip()); } +quic::QuicIpAddress +parseIpAddressFromSocketAddress(const envoy::config::core::v3::SocketAddress& addr, + Network::Address::IpVersion version, absl::string_view version_str, + const Protobuf::Message& message) { + auto socket_addr = parseSocketAddress(addr, version, version_str, message); + if (socket_addr.port() != 0) { + ProtoExceptionUtil::throwProtoValidationException( + fmt::format("port must be 0 in this version of Envoy in address '{}'", + socket_addr.ToString()), + message); + } + + return socket_addr.host(); +} + +FixedServerPreferredAddressConfig::FamilyAddresses +parseFamily(const std::string& addr_string, + const envoy::extensions::quic::server_preferred_address::v3:: + FixedServerPreferredAddressConfig::Addresses* addresses, + Network::Address::IpVersion version, absl::string_view address_family, + const Protobuf::Message& message) { + FixedServerPreferredAddressConfig::FamilyAddresses ret; + if (addresses != nullptr) { + if (addresses->has_dnat_address() && !addresses->has_address()) { + ProtoExceptionUtil::throwProtoValidationException( + absl::StrCat("'dnat_address' but not 'address' is set in server preferred address for ", + address_family), + message); + } + if (addresses->has_address()) { + ret.spa_ = parseSocketAddress(addresses->address(), version, address_family, message); + } + if (addresses->has_dnat_address()) { + ret.dnat_ = parseIpAddressFromSocketAddress(addresses->dnat_address(), version, + address_family, message); + } + } else { + if (!addr_string.empty()) { + ret.spa_ = quic::QuicSocketAddress(parseIp(addr_string, address_family, message), 0); + } + } + + return ret; +} + } // namespace EnvoyQuicServerPreferredAddressConfig::Addresses @@ -66,10 +98,10 @@ FixedServerPreferredAddressConfig::getServerPreferredAddresses( const Network::Address::InstanceConstSharedPtr& local_address) { int32_t port = local_address->ip()->port(); Addresses addresses; - addresses.ipv4_ = ipOrAddressToAddress(ip_v4_, port); - addresses.ipv6_ = ipOrAddressToAddress(ip_v6_, port); - addresses.dnat_ipv4_ = quic::QuicSocketAddress(dnat_ip_v4_, port); - addresses.dnat_ipv6_ = quic::QuicSocketAddress(dnat_ip_v6_, port); + addresses.ipv4_ = ipOrAddressToAddress(v4_.spa_, port); + addresses.ipv6_ = ipOrAddressToAddress(v6_.spa_, port); + addresses.dnat_ipv4_ = quic::QuicSocketAddress(v4_.dnat_, port); + addresses.dnat_ipv6_ = quic::QuicSocketAddress(v6_.dnat_, port); return addresses; } @@ -81,47 +113,15 @@ FixedServerPreferredAddressConfigFactory::createServerPreferredAddressConfig( MessageUtil::downcastAndValidate(message, validation_visitor); - FixedServerPreferredAddressConfig::QuicSocketOrIpAddress ip_v4, ip_v6; - switch (config.ipv4_type_case()) { - case envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig:: - kIpv4Address: - ip_v4 = parseIp(config.ipv4_address(), "v4", message); - break; - case envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig:: - kIpv4AddressAndPort: - ip_v4 = parseSocketAddress(config.ipv4_address_and_port(), Network::Address::IpVersion::v4, - "v4", message); - break; - case envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig:: - IPV4_TYPE_NOT_SET: - break; - } - switch (config.ipv6_type_case()) { - case envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig:: - kIpv6Address: - ip_v6 = parseIp(config.ipv6_address(), "v6", message); - break; - case envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig:: - kIpv6AddressAndPort: - ip_v6 = parseSocketAddress(config.ipv6_address_and_port(), Network::Address::IpVersion::v6, - "v6", message); - break; - case envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig:: - IPV6_TYPE_NOT_SET: - break; - } - - quic::QuicIpAddress dnat_v4, dnat_v6; - if (config.has_ipv4_dnat_address()) { - dnat_v4 = parseIp(config.ipv4_dnat_address(), "dnat v4", message); - } - - if (config.has_ipv6_dnat_address()) { - dnat_v6 = parseIp(config.ipv6_dnat_address(), "dnat v6", message); - } + auto v4 = + parseFamily(config.ipv4_address(), config.has_ipv4_config() ? &config.ipv4_config() : nullptr, + Network::Address::IpVersion::v4, "v4", message); + auto v6 = + parseFamily(config.ipv6_address(), config.has_ipv6_config() ? &config.ipv6_config() : nullptr, + Network::Address::IpVersion::v6, "v6", message); - return std::make_unique(ip_v4, ip_v6, dnat_v4, dnat_v6); + return std::make_unique(v4, v6); } REGISTER_FACTORY(FixedServerPreferredAddressConfigFactory, diff --git a/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.h b/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.h index 292610428fc5..0ec0cc2bc7b8 100644 --- a/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.h +++ b/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.h @@ -11,22 +11,20 @@ namespace Quic { class FixedServerPreferredAddressConfig : public Quic::EnvoyQuicServerPreferredAddressConfig { public: - using QuicSocketOrIpAddress = absl::variant; + struct FamilyAddresses { + quic::QuicSocketAddress spa_; + quic::QuicIpAddress dnat_; + }; - FixedServerPreferredAddressConfig(const QuicSocketOrIpAddress& ipv4, - const QuicSocketOrIpAddress& ipv6, - const quic::QuicIpAddress ipv4_dnat, - const quic::QuicIpAddress ipv6_dnat) - : ip_v4_(ipv4), ip_v6_(ipv6), dnat_ip_v4_(ipv4_dnat), dnat_ip_v6_(ipv6_dnat) {} + FixedServerPreferredAddressConfig(const FamilyAddresses& v4, const FamilyAddresses& v6) + : v4_(v4), v6_(v6) {} Addresses getServerPreferredAddresses( const Network::Address::InstanceConstSharedPtr& local_address) override; private: - const QuicSocketOrIpAddress ip_v4_; - const QuicSocketOrIpAddress ip_v6_; - const quic::QuicIpAddress dnat_ip_v4_; - const quic::QuicIpAddress dnat_ip_v6_; + const FamilyAddresses v4_; + const FamilyAddresses v6_; }; class FixedServerPreferredAddressConfigFactory diff --git a/test/extensions/quic/server_preferred_address/fixed_server_preferred_address_test.cc b/test/extensions/quic/server_preferred_address/fixed_server_preferred_address_test.cc index c3dcf1414784..5cda881d83ea 100644 --- a/test/extensions/quic/server_preferred_address/fixed_server_preferred_address_test.cc +++ b/test/extensions/quic/server_preferred_address/fixed_server_preferred_address_test.cc @@ -20,8 +20,8 @@ TEST_F(FixedServerPreferredAddressConfigTest, Validation) { { // Bad address_and_port. envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig cfg; - cfg.mutable_ipv4_address_and_port()->set_address("not an address"); - cfg.mutable_ipv4_address_and_port()->set_port_value(1); + cfg.mutable_ipv4_config()->mutable_address()->set_address("not an address"); + cfg.mutable_ipv4_config()->mutable_address()->set_port_value(1); EXPECT_THROW_WITH_REGEX(factory_.createServerPreferredAddressConfig(cfg, visitor_, {}), EnvoyException, ".*malformed IP address: not an address.*"); } @@ -32,11 +32,31 @@ TEST_F(FixedServerPreferredAddressConfigTest, Validation) { EXPECT_THROW_WITH_REGEX(factory_.createServerPreferredAddressConfig(cfg, visitor_, {}), EnvoyException, ".*bad v4 server preferred address: not an address.*"); } + { + // Non-zero port not supported in dnat address. + envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig cfg; + cfg.mutable_ipv4_config()->mutable_address()->set_address("127.0.0.1"); + cfg.mutable_ipv4_config()->mutable_address()->set_port_value(1); + cfg.mutable_ipv4_config()->mutable_dnat_address()->set_address("127.0.0.1"); + cfg.mutable_ipv4_config()->mutable_dnat_address()->set_port_value(1); + EXPECT_THROW_WITH_REGEX(factory_.createServerPreferredAddressConfig(cfg, visitor_, {}), + EnvoyException, + ".*port must be 0 in this version of Envoy in address '127.0.0.1:1'.*"); + } + { + // Cannot set dnat address but not spa address. + envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig cfg; + cfg.mutable_ipv4_config()->mutable_dnat_address()->set_address("127.0.0.1"); + cfg.mutable_ipv4_config()->mutable_dnat_address()->set_port_value(1); + EXPECT_THROW_WITH_REGEX( + factory_.createServerPreferredAddressConfig(cfg, visitor_, {}), EnvoyException, + ".*'dnat_address' but not 'address' is set in server preferred address for v4.*"); + } { // v6 address in v4 field. envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig cfg; - cfg.mutable_ipv4_address_and_port()->set_address("::1"); - cfg.mutable_ipv4_address_and_port()->set_port_value(1); + cfg.mutable_ipv4_config()->mutable_address()->set_address("::1"); + cfg.mutable_ipv4_config()->mutable_address()->set_port_value(1); EXPECT_THROW_WITH_REGEX(factory_.createServerPreferredAddressConfig(cfg, visitor_, {}), EnvoyException, ".*wrong address type for v4 server preferred address.*"); @@ -44,8 +64,8 @@ TEST_F(FixedServerPreferredAddressConfigTest, Validation) { { // v4 address in v6 field. envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig cfg; - cfg.mutable_ipv6_address_and_port()->set_address("127.0.0.1"); - cfg.mutable_ipv6_address_and_port()->set_port_value(1); + cfg.mutable_ipv6_config()->mutable_address()->set_address("127.0.0.1"); + cfg.mutable_ipv6_config()->mutable_address()->set_port_value(1); EXPECT_THROW_WITH_REGEX(factory_.createServerPreferredAddressConfig(cfg, visitor_, {}), EnvoyException, ".*wrong address type for v6 server preferred address.*"); @@ -61,15 +81,25 @@ TEST_F(FixedServerPreferredAddressConfigTest, AddressGetsCombinedWithPort) { EXPECT_EQ(addresses.ipv4_.ToString(), "1.2.3.4:1234"); } -TEST_F(FixedServerPreferredAddressConfigTest, AddressAndPortIgnoresPort) { +TEST_F(FixedServerPreferredAddressConfigTest, AddressAndPortIgnoresListenerPort) { envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig cfg; - cfg.mutable_ipv4_address_and_port()->set_address("1.2.3.4"); - cfg.mutable_ipv4_address_and_port()->set_port_value(5); + cfg.mutable_ipv4_config()->mutable_address()->set_address("1.2.3.4"); + cfg.mutable_ipv4_config()->mutable_address()->set_port_value(5); auto obj = factory_.createServerPreferredAddressConfig(cfg, visitor_, {}); auto addresses = obj->getServerPreferredAddresses( Network::Utility::parseInternetAddressNoThrow("127.0.0.1", 1234)); EXPECT_EQ(addresses.ipv4_.ToString(), "1.2.3.4:5"); } +TEST_F(FixedServerPreferredAddressConfigTest, AddressAndZeroPortUsesListenerPort) { + envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig cfg; + cfg.mutable_ipv4_config()->mutable_address()->set_address("1.2.3.4"); + cfg.mutable_ipv4_config()->mutable_address()->set_port_value(0); + auto obj = factory_.createServerPreferredAddressConfig(cfg, visitor_, {}); + auto addresses = obj->getServerPreferredAddresses( + Network::Utility::parseInternetAddressNoThrow("127.0.0.1", 1234)); + EXPECT_EQ(addresses.ipv4_.ToString(), "1.2.3.4:1234"); +} + } // namespace Quic } // namespace Envoy diff --git a/test/integration/quic_http_integration_test.cc b/test/integration/quic_http_integration_test.cc index 94308d05594c..b2c4e7a4cd05 100644 --- a/test/integration/quic_http_integration_test.cc +++ b/test/integration/quic_http_integration_test.cc @@ -1955,12 +1955,16 @@ TEST_P(QuicHttpIntegrationTest, UsesPreferredAddressDNAT) { preferred_address_config->set_name("quic.server_preferred_address.fixed"); envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig server_preferred_address; - server_preferred_address.mutable_ipv4_address_and_port()->set_address("1.2.3.4"); - server_preferred_address.mutable_ipv4_address_and_port()->set_port_value(12345); - server_preferred_address.mutable_ipv6_address_and_port()->set_address("::1"); - server_preferred_address.mutable_ipv6_address_and_port()->set_port_value(12345); - server_preferred_address.set_ipv4_dnat_address("127.0.0.2"); - server_preferred_address.set_ipv6_dnat_address("::2"); + server_preferred_address.mutable_ipv4_config()->mutable_address()->set_address("1.2.3.4"); + server_preferred_address.mutable_ipv4_config()->mutable_address()->set_port_value(12345); + server_preferred_address.mutable_ipv4_config()->mutable_dnat_address()->set_address( + "127.0.0.2"); + server_preferred_address.mutable_ipv4_config()->mutable_dnat_address()->set_port_value(0); + + server_preferred_address.mutable_ipv6_config()->mutable_address()->set_address("::1"); + server_preferred_address.mutable_ipv6_config()->mutable_address()->set_port_value(12345); + server_preferred_address.mutable_ipv6_config()->mutable_dnat_address()->set_address("::2"); + server_preferred_address.mutable_ipv6_config()->mutable_dnat_address()->set_port_value(0); preferred_address_config->mutable_typed_config()->PackFrom(server_preferred_address); // Configure a test listener filter which is incompatible with any server preferred From 19c2d6653fda1872e11552da883885289b682a0b Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Tue, 30 Apr 2024 20:18:33 -0700 Subject: [PATCH 11/22] revert accidental change Signed-off-by: Greg Greenway --- configs/envoyproxy_io_proxy_http3_downstream.yaml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/configs/envoyproxy_io_proxy_http3_downstream.yaml b/configs/envoyproxy_io_proxy_http3_downstream.yaml index 79e3dd1a3284..eaba8f8d88f0 100644 --- a/configs/envoyproxy_io_proxy_http3_downstream.yaml +++ b/configs/envoyproxy_io_proxy_http3_downstream.yaml @@ -56,14 +56,8 @@ static_resources: address: socket_address: protocol: UDP - address: 127.0.0.1 + address: 0.0.0.0 port_value: 10000 - additional_addresses: - - address: - socket_address: - protocol: UDP - address: 127.0.0.1 - port_value: 10001 udp_listener_config: quic_options: {} downstream_socket_config: From cf30494c908aeb0f5b764181396cbfc8c5227831 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Wed, 1 May 2024 08:15:59 -0700 Subject: [PATCH 12/22] proto format Signed-off-by: Greg Greenway --- .../v3/fixed_server_preferred_address_config.proto | 1 - 1 file changed, 1 deletion(-) diff --git a/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto b/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto index 45f8a281afc1..439101c26b4e 100644 --- a/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto +++ b/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto @@ -18,7 +18,6 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#extension: envoy.quic.server_preferred_address.fixed] // Configuration for FixedServerPreferredAddressConfig. -// [#next-free-field: 7] message FixedServerPreferredAddressConfig { // [#comment:TODO(danzh2010): discuss with API shepherds before removing WiP status.] From c7bf130a3ef22242231dd10f4256784ac5af39e8 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Wed, 1 May 2024 08:17:08 -0700 Subject: [PATCH 13/22] spelling Signed-off-by: Greg Greenway --- .../v3/fixed_server_preferred_address_config.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto b/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto index 439101c26b4e..350553f2350c 100644 --- a/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto +++ b/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto @@ -23,7 +23,7 @@ message FixedServerPreferredAddressConfig { option (xds.annotations.v3.message_status).work_in_progress = true; - // Addreses for server preferred address for a single address family (IPv4 or IPv6). + // Addresses for server preferred address for a single address family (IPv4 or IPv6). message Addresses { // The server preferred address sent to clients. // From 81b10a2b0736bd808fd32af2f36233c54fb21ed3 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Wed, 1 May 2024 08:40:27 -0700 Subject: [PATCH 14/22] fix single backtick proto format error Signed-off-by: Greg Greenway --- .../v3/fixed_server_preferred_address_config.proto | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto b/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto index 350553f2350c..50aee5022796 100644 --- a/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto +++ b/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto @@ -27,8 +27,9 @@ message FixedServerPreferredAddressConfig { message Addresses { // The server preferred address sent to clients. // - // Note: Envoy currently must receive all packets for a QUIC connection on the same port, so - // unless `dnat_address` is configured, the port for this address must be zero, and the listener's + // Note: Envoy currently must receive all packets for a QUIC connection on the same port, so unless + // :ref:`dnat_address ` + // is configured, the port for this address must be zero, and the listener's // port will be used instead. config.core.v3.SocketAddress address = 1; From 9c8cfba1a194984c1d2ef2455bf11a56254b493c Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Wed, 1 May 2024 08:40:41 -0700 Subject: [PATCH 15/22] build link in release notes Signed-off-by: Greg Greenway --- changelogs/current.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 77b06d687be7..acc297fc02d1 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -65,7 +65,7 @@ new_features: - area: quic change: | Added support for QUIC server preferred address when there is a DNAT between the client and Envoy. See - :ref:`new config `. + :ref:`new config `. - area: cares change: | Added :ref:`udp_max_queries` From d8adc70540c65bc704d6477247d6c5e30ba0b0c6 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Wed, 1 May 2024 08:40:56 -0700 Subject: [PATCH 16/22] add missing validation of spa port being zero for non-dnat Signed-off-by: Greg Greenway --- .../fixed_server_preferred_address_config.cc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc b/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc index 36d9d9b5e7a6..4f9b78a619df 100644 --- a/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc +++ b/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc @@ -75,9 +75,19 @@ parseFamily(const std::string& addr_string, address_family), message); } + if (addresses->has_address()) { ret.spa_ = parseSocketAddress(addresses->address(), version, address_family, message); + + if (!addresses->has_dnat_address() && ret.spa_.port() != 0) { + ProtoExceptionUtil::throwProtoValidationException( + fmt::format("'address' port must be zero unless 'dnat_address' is set in address {} " + "for address family {}", + ret.spa_.ToString(), address_family), + message); + } } + if (addresses->has_dnat_address()) { ret.dnat_ = parseIpAddressFromSocketAddress(addresses->dnat_address(), version, address_family, message); From 59ded87c5f16fe14f22573cfb7e1b83e5b34748a Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Wed, 1 May 2024 08:41:22 -0700 Subject: [PATCH 17/22] more tests Signed-off-by: Greg Greenway --- .../fixed_server_preferred_address_test.cc | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/test/extensions/quic/server_preferred_address/fixed_server_preferred_address_test.cc b/test/extensions/quic/server_preferred_address/fixed_server_preferred_address_test.cc index 5cda881d83ea..75aafba17c2e 100644 --- a/test/extensions/quic/server_preferred_address/fixed_server_preferred_address_test.cc +++ b/test/extensions/quic/server_preferred_address/fixed_server_preferred_address_test.cc @@ -52,6 +52,16 @@ TEST_F(FixedServerPreferredAddressConfigTest, Validation) { factory_.createServerPreferredAddressConfig(cfg, visitor_, {}), EnvoyException, ".*'dnat_address' but not 'address' is set in server preferred address for v4.*"); } + { + // Cannot set port on address if dnat address isn't set. + envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig cfg; + cfg.mutable_ipv4_config()->mutable_address()->set_address("127.0.0.1"); + cfg.mutable_ipv4_config()->mutable_address()->set_port_value(1); + EXPECT_THROW_WITH_REGEX(factory_.createServerPreferredAddressConfig(cfg, visitor_, {}), + EnvoyException, + ".*'address' port must be zero unless 'dnat_address' is set in address " + "127.0.0.1:1 for address family v4.*"); + } { // v6 address in v4 field. envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig cfg; @@ -85,6 +95,8 @@ TEST_F(FixedServerPreferredAddressConfigTest, AddressAndPortIgnoresListenerPort) envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig cfg; cfg.mutable_ipv4_config()->mutable_address()->set_address("1.2.3.4"); cfg.mutable_ipv4_config()->mutable_address()->set_port_value(5); + cfg.mutable_ipv4_config()->mutable_dnat_address()->set_address("127.0.0.1"); + cfg.mutable_ipv4_config()->mutable_dnat_address()->set_port_value(0); auto obj = factory_.createServerPreferredAddressConfig(cfg, visitor_, {}); auto addresses = obj->getServerPreferredAddresses( Network::Utility::parseInternetAddressNoThrow("127.0.0.1", 1234)); @@ -101,5 +113,39 @@ TEST_F(FixedServerPreferredAddressConfigTest, AddressAndZeroPortUsesListenerPort EXPECT_EQ(addresses.ipv4_.ToString(), "1.2.3.4:1234"); } +TEST_F(FixedServerPreferredAddressConfigTest, DnatAddressAndZeroPortUsesListenerPort) { + envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig cfg; + cfg.mutable_ipv4_config()->mutable_address()->set_address("1.2.3.4"); + cfg.mutable_ipv4_config()->mutable_address()->set_port_value(0); + cfg.mutable_ipv4_config()->mutable_dnat_address()->set_address("127.0.0.1"); + cfg.mutable_ipv4_config()->mutable_dnat_address()->set_port_value(0); + auto obj = factory_.createServerPreferredAddressConfig(cfg, visitor_, {}); + auto addresses = obj->getServerPreferredAddresses( + Network::Utility::parseInternetAddressNoThrow("127.0.0.1", 1234)); + EXPECT_EQ(addresses.dnat_ipv4_.ToString(), "127.0.0.1:1234"); +} + +// `ipv4_config` is preferred over `ipv4_address` if both are set. +TEST_F(FixedServerPreferredAddressConfigTest, FieldPrecedence) { + envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig cfg; + cfg.set_ipv4_address("2.2.2.2"); + cfg.mutable_ipv4_config()->mutable_address()->set_address("1.2.3.4"); + cfg.mutable_ipv4_config()->mutable_address()->set_port_value(0); + auto obj = factory_.createServerPreferredAddressConfig(cfg, visitor_, {}); + auto addresses = obj->getServerPreferredAddresses( + Network::Utility::parseInternetAddressNoThrow("127.0.0.1", 1234)); + EXPECT_EQ(addresses.ipv4_.ToString(), "1.2.3.4:1234"); +} + +// If only `ipv4_address` is set, it is used. +TEST_F(FixedServerPreferredAddressConfigTest, LegacyField) { + envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig cfg; + cfg.set_ipv4_address("2.2.2.2"); + auto obj = factory_.createServerPreferredAddressConfig(cfg, visitor_, {}); + auto addresses = obj->getServerPreferredAddresses( + Network::Utility::parseInternetAddressNoThrow("127.0.0.1", 1234)); + EXPECT_EQ(addresses.ipv4_.ToString(), "2.2.2.2:1234"); +} + } // namespace Quic } // namespace Envoy From 8c7e9728f87fe74a36cb04452c4e5764df74d852 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Wed, 1 May 2024 09:06:07 -0700 Subject: [PATCH 18/22] fix format of release notes Signed-off-by: Greg Greenway --- changelogs/current.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index acc297fc02d1..8aae385e7993 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -65,7 +65,8 @@ new_features: - area: quic change: | Added support for QUIC server preferred address when there is a DNAT between the client and Envoy. See - :ref:`new config `. + :ref:`new config + `. - area: cares change: | Added :ref:`udp_max_queries` From 155e2ef895d6c4f17c2bf431767ea4339c75f2af Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Wed, 1 May 2024 09:19:11 -0700 Subject: [PATCH 19/22] more proto format fixes Signed-off-by: Greg Greenway --- .../v3/fixed_server_preferred_address_config.proto | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto b/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto index 50aee5022796..6817eb40d490 100644 --- a/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto +++ b/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto @@ -49,7 +49,8 @@ message FixedServerPreferredAddressConfig { string ipv4_address = 1; // The IPv4 address to advertise to clients for Server Preferred Address. - // This field takes precedence over `ipv4_address`. + // This field takes precedence over + // :ref:`ipv4_address `. Addresses ipv4_config = 3; // String representation of IPv6 address, i.e. "::1". @@ -58,6 +59,7 @@ message FixedServerPreferredAddressConfig { string ipv6_address = 2; // The IPv6 address to advertise to clients for Server Preferred Address. - // This field takes precedence over `ipv6_address`. + // This field takes precedence over + // :ref:`ipv6_address `. Addresses ipv6_config = 4; } From 40d2ab5216cd6c0e608e3d7abf31e4e81c0b14c8 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Mon, 6 May 2024 11:31:40 -0700 Subject: [PATCH 20/22] rename proto message Signed-off-by: Greg Greenway --- .../v3/fixed_server_preferred_address_config.proto | 8 ++++---- changelogs/current.yaml | 2 +- .../fixed_server_preferred_address_config.cc | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto b/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto index 6817eb40d490..66632b9bc5de 100644 --- a/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto +++ b/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto @@ -24,11 +24,11 @@ message FixedServerPreferredAddressConfig { option (xds.annotations.v3.message_status).work_in_progress = true; // Addresses for server preferred address for a single address family (IPv4 or IPv6). - message Addresses { + message AddressFamilyConfig { // The server preferred address sent to clients. // // Note: Envoy currently must receive all packets for a QUIC connection on the same port, so unless - // :ref:`dnat_address ` + // :ref:`dnat_address ` // is configured, the port for this address must be zero, and the listener's // port will be used instead. config.core.v3.SocketAddress address = 1; @@ -51,7 +51,7 @@ message FixedServerPreferredAddressConfig { // The IPv4 address to advertise to clients for Server Preferred Address. // This field takes precedence over // :ref:`ipv4_address `. - Addresses ipv4_config = 3; + AddressFamilyConfig ipv4_config = 3; // String representation of IPv6 address, i.e. "::1". // The listener's port will be used. @@ -61,5 +61,5 @@ message FixedServerPreferredAddressConfig { // The IPv6 address to advertise to clients for Server Preferred Address. // This field takes precedence over // :ref:`ipv6_address `. - Addresses ipv6_config = 4; + AddressFamilyConfig ipv6_config = 4; } diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 8aae385e7993..278e283ccf2c 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -66,7 +66,7 @@ new_features: change: | Added support for QUIC server preferred address when there is a DNAT between the client and Envoy. See :ref:`new config - `. + `. - area: cares change: | Added :ref:`udp_max_queries` diff --git a/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc b/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc index 4f9b78a619df..6163bf11178b 100644 --- a/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc +++ b/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc @@ -64,7 +64,7 @@ parseIpAddressFromSocketAddress(const envoy::config::core::v3::SocketAddress& ad FixedServerPreferredAddressConfig::FamilyAddresses parseFamily(const std::string& addr_string, const envoy::extensions::quic::server_preferred_address::v3:: - FixedServerPreferredAddressConfig::Addresses* addresses, + FixedServerPreferredAddressConfig::AddressFamilyConfig* addresses, Network::Address::IpVersion version, absl::string_view address_family, const Protobuf::Message& message) { FixedServerPreferredAddressConfig::FamilyAddresses ret; From 81241b761291e22e20d4b31075be47e05fee0fed Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Mon, 6 May 2024 12:07:56 -0700 Subject: [PATCH 21/22] remote mention of listener port in docs Signed-off-by: Greg Greenway --- .../v3/fixed_server_preferred_address_config.proto | 2 -- 1 file changed, 2 deletions(-) diff --git a/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto b/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto index 66632b9bc5de..28d6cd57873f 100644 --- a/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto +++ b/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto @@ -44,7 +44,6 @@ message FixedServerPreferredAddressConfig { } // String representation of IPv4 address, i.e. "127.0.0.2". - // The listener's port will be used. // If not specified, none will be configured. string ipv4_address = 1; @@ -54,7 +53,6 @@ message FixedServerPreferredAddressConfig { AddressFamilyConfig ipv4_config = 3; // String representation of IPv6 address, i.e. "::1". - // The listener's port will be used. // If not specified, none will be configured. string ipv6_address = 2; From 39ca689736ceadddd70f5be916498f859d5a373d Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Mon, 6 May 2024 15:02:15 -0700 Subject: [PATCH 22/22] auto->type Signed-off-by: Greg Greenway --- .../fixed_server_preferred_address_config.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc b/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc index 6163bf11178b..805771dcfeef 100644 --- a/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc +++ b/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc @@ -124,10 +124,10 @@ FixedServerPreferredAddressConfigFactory::createServerPreferredAddressConfig( FixedServerPreferredAddressConfig&>(message, validation_visitor); - auto v4 = + FixedServerPreferredAddressConfig::FamilyAddresses v4 = parseFamily(config.ipv4_address(), config.has_ipv4_config() ? &config.ipv4_config() : nullptr, Network::Address::IpVersion::v4, "v4", message); - auto v6 = + FixedServerPreferredAddressConfig::FamilyAddresses v6 = parseFamily(config.ipv6_address(), config.has_ipv6_config() ? &config.ipv6_config() : nullptr, Network::Address::IpVersion::v6, "v6", message);