-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
quic: support for server-preferred address behind DNAT #33774
Conversation
Signed-off-by: Greg Greenway <ggreenway@apple.com>
CC @envoyproxy/api-shepherds: Your approval is needed for changes made to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! Thanks for doing this.
// If not specified, none will be configured. | ||
string ipv4_address = 1; | ||
|
||
// The IPv4 address to advertise to clients for Server Preferred Address. | ||
config.core.v3.SocketAddress ipv4_address_and_port = 3; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of curiosity, why do we need a string and a struct version of this field?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The string version does not allow setting the port. I think it was done this way because we don't yet support multiple sockets on a quic listener; it has to get the original and spa traffic on the same socket, bound to 0.0.0.0:443
or similar, so the SPA address must use the same port, and so it isn't allowed to be configured here.
I am hoping to allow multiple sockets in the future, but this is what we have for now.
But with DNAT, the client's view of the destination may not be what the server receives, so if the DNAT also does port translation, we can now support having a different port in the address advertised to clients.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We had a string version because we don't want to make port part of the config as the port is supposed to be the same as the listening port.
With DNAT do we need to advertise a different port in transport parameter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since you added a new knob to allow a different port. I think we can keep the old behavior of the string form with a comment "if it is desired to advertise a different port, use the struct form".
As to the new knob, how about defining a struct
DNatAddressPair {
// Port will be used.
config.core.v3.SocketAddress address_to_advertise;
// Only the IP part. Different port is not supported yet.
string dnat_internal_address;
};
And then
oneof ipv4_type {
// String representation of IPv4 address, i.e. "127.0.0.2".
// Port will not be used.
string ipv4_address = 1;
DNatAddressPair ipv4_addresses_with_dnat = 2;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that the current restriction on the port being equal is an implementation restriction, that may be alleviated in the future, would it be better to use SocketAddress
for both addresses, and document that the port is currently ignored, so that we don't have to change proto in the future to allow setting the port if/when that happens?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SG. And please also emit an error during config validation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I pushed a commit with what I think we've agreed on for the config. @danzh2010 @abeyad can you please take a look and confirm this looks roughly correct; then I'll make the related code changes (and add the validation that the port == 0 in the cases where it isn't/can't be used).
@@ -15,14 +15,28 @@ class EnvoyQuicServerPreferredAddressConfig { | |||
public: | |||
virtual ~EnvoyQuicServerPreferredAddressConfig() = default; | |||
|
|||
struct Addresses { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Brief comment, please.
// 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment is super clear. Thanks!
using T = std::decay_t<decltype(arg)>; | ||
if constexpr (std::is_same_v<T, quic::QuicSocketAddress>) { | ||
return arg; | ||
} else if constexpr (std::is_same_v<T, quic::QuicIpAddress>) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Since each of these branches is returning, let's remove the "} else" which in particular removes the indentation for the last case.
address); | ||
} | ||
|
||
quic::QuicIpAddress parseIp(const std::string& addr, absl::string_view version, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: "version" => "ip_version" or "address_family"
Signed-off-by: Greg Greenway <ggreenway@apple.com>
Signed-off-by: Greg Greenway <ggreenway@apple.com>
Signed-off-by: Greg Greenway <ggreenway@apple.com>
// | ||
// String representation of IPv6 address, i.e. "::1" | ||
// The listener's port will be used. | ||
string ipv6_dnat_address = 6; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oneof
is now discouraged:
Line 63 in 21b406b
* Prefer multiple fields with defined precedence over boolean overloads of fields or |
Is it necessary in this case? Do we anticipate more fields to select from?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm trying to mirror the other field here. It'll probably need a similar set of options as ipv6_address
is getting in this PR. See #33774 (comment) for discussion on why it is this way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking about this a little further, I think using the config.core.v3.Address is a better type for this data, and in the cases where we currently cannot specify a port, ignore it (or throw a validation error if it isn't zero). Then we can deprecate the existing string version, and stop using the oneof's at all. Wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@abeyad and @RyanTheOptimist would it be better like this (plus the equivalent for ipv6)?
oneof ipv4_type {
// Deprecated
string ipv4_address = 1;
}
// The IPv4 address to advertise to clients for Server Preferred Address.
config.core.v3.SocketAddress ipv4_address_and_port = 3;
// Port isn't yet available; must be zero or ignored
config.core.v3.SocketAddress ipv4_dnat_address = 5;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to make sure I understand, ipv4_dnat_type
and ipv6_dnat_type
would go away, and we'd just have:
config.core.v3.Address ipv4_dnat_address;
config.core.v3.Address ipv6_dnat_address;
is that correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That SGTM, thanks @ggreenway !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need port in non-DNAT case? Even in DNAT case, the client doesn't see a different port right? Only the translation unit will translate [SPA:443] to something else [configured_address:port], is this correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the DNAT is translating both IP and port, then the client-advertised address may need a different port than the listener.
Also, I'm considering adding support for quic listeners to support additional_addresses
; if that were true then in the non-DNAT case, the client-advertised address may also need a different port, and the DNAT address may need a different port.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
QUICHE doesn't support multiple writers for a server connection. Is there a use case for advertising a different port for SPA?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The use case is a host in AWS with a single NIC and IP, with both an NLB for initial connection load balancing, and an external EIP for SPA traffic. If the two paths go to different ports, envoy/quiche can easily differentiate which path the traffic is coming from. If they go to the same port, envoy/quiche sees the client attempting SPA as the client trying to do a voluntary migration during the initial handshake.
Signed-off-by: Greg Greenway <ggreenway@apple.com>
Signed-off-by: Greg Greenway <ggreenway@apple.com>
/wait |
Signed-off-by: Greg Greenway <ggreenway@apple.com>
message FixedServerPreferredAddressConfig { | ||
// [#comment:TODO(danzh2010): discuss with API shepherds before removing WiP status.] | ||
|
||
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 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nits: how about AddressPair?
// 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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nits: how about ipv4_addresses_with_dnat
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because if we allow setting a different port, without dnat, then this config will be used in a non-dnat case, and the dnat address in the message will be left unset. I'm trying to future-proof the config so we don't need to change it again.
// 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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The structure of all this LGTM.
I'm unsure if we should try to move the new Address
type fields out of the oneof
though, in keeping with the new API style guidance discouraging oneof
in favor of defining precedence rules (e.g. this PR does that for an existing API https://github.com/envoyproxy/envoy/pull/33749/files#diff-e18722c3190aeb921472834ba2f4725bcfa5dfae5c024d9d7aca8cd2ecefeaee).
That would mean keeping the oneof
, but defining ipv4_config
and ipv6_config
as separate fields outside of the oneof
and using precedence rules that if ipv6_config (or ipv4_config) is defined, use that, otherwise use ipv6_address (or ipv4_address).
Would that work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pushed another version, which I think changes it as you're requesting. PTAL.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are any of the normal rules for changing protos relaxed due to option (xds.annotations.v3.message_status).work_in_progress = true;
? Could we remove the oneof wrapper, since nothing else will ever go into it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks Greg!
If it's marked work_in_progress, we could remove the oneof
AFAIU. In https://github.com/envoyproxy/envoy/blob/main/api/STYLE.md, we have this clause:
Use a (xds.annotations.v3.file_status).work_in_progress, (xds.annotations.v3.message_status).work_in_progress, or (xds.annotations.v3.field_status).work_in_progress option annotation for files, messages, or fields, respectively, that are considered work in progress and are not subject to the threat model or the breaking change policy.
So we should be able to change the oneof
since it's marked as work_in_progress.
cc @envoyproxy/api-shepherds in case there is a differing opinion on this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current structure looks good, other than a nits for renaming. Having oneof
also makes sense to me, is there a reason why we want to remove it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oneof
is now discouraged by the API style guide: https://github.com/envoyproxy/envoy/blob/main/api/STYLE.md?plain=1#L63-L90
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@danzh2010 I didn't want to change the names to be overly specific in case we need to add new fields, and they don't work well with those very specific names. For Addresses
, maybe is could be AddressFamilyConfig
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AddressFamilyConfig sounds better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed name to AddressFamilyConfig
Signed-off-by: Greg Greenway <ggreenway@apple.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/lgtm api
Signed-off-by: Greg Greenway <ggreenway@apple.com>
Signed-off-by: Greg Greenway <ggreenway@apple.com>
Signed-off-by: Greg Greenway <ggreenway@apple.com>
Signed-off-by: Greg Greenway <ggreenway@apple.com>
Signed-off-by: Greg Greenway <ggreenway@apple.com>
Signed-off-by: Greg Greenway <ggreenway@apple.com>
Signed-off-by: Greg Greenway <ggreenway@apple.com>
Signed-off-by: Greg Greenway <ggreenway@apple.com>
@danzh2010 does this LG to you? |
Signed-off-by: Greg Greenway <ggreenway@apple.com>
...nvoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto
Outdated
Show resolved
Hide resolved
...nvoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto
Outdated
Show resolved
Hide resolved
/** | ||
* Called during config loading. | ||
* @param local_address the configured default listening address. | ||
* Returns a pair of the server preferred addresses in form of {IPv4, IPv6} which will be used for | ||
* the entire life time of the QUIC listener. An uninitialized address value means no preferred | ||
* address for that address family. | ||
*/ | ||
virtual std::pair<quic::QuicSocketAddress, quic::QuicSocketAddress> | ||
virtual Addresses |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this interface needs to be changed? And why the proto struct is more preferrable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because it is now returning 4 addresses instead of two. And a std::tuple
of 4 elements is harder to read than a struct with four elements.
Signed-off-by: Greg Greenway <ggreenway@apple.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, along aside a small nit. Thanks for contributing to this API!
source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc
Outdated
Show resolved
Hide resolved
Signed-off-by: Greg Greenway <ggreenway@apple.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/lgtm api
@RyanTheOptimist please review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for doing this!
Commit Message:
Additional Description:
Risk Level: Low
Testing: Added integration test
Docs Changes: Documented in proto
Release Notes: added
Platform Specific Features:
[Optional Runtime guard:]
[Optional Fixes #Issue]
[Optional Fixes commit #PR or SHA]
[Optional Deprecated:]
[Optional API Considerations:]