Skip to content
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

Merged
merged 23 commits into from May 7, 2024

Conversation

ggreenway
Copy link
Contributor

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:]

Signed-off-by: Greg Greenway <ggreenway@apple.com>
Copy link

CC @envoyproxy/api-shepherds: Your approval is needed for changes made to (api/envoy/|docs/root/api-docs/).
envoyproxy/api-shepherds assignee is @abeyad
CC @envoyproxy/api-watchers: FYI only for changes made to (api/envoy/|docs/root/api-docs/).

🐱

Caused by: #33774 was opened by ggreenway.

see: more, trace.

Signed-off-by: Greg Greenway <ggreenway@apple.com>
Signed-off-by: Greg Greenway <ggreenway@apple.com>
Copy link
Contributor

@RyanTheOptimist RyanTheOptimist left a 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;
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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?

Copy link
Contributor

@danzh2010 danzh2010 Apr 30, 2024

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;
}

Copy link
Contributor Author

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?

Copy link
Contributor

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.

Copy link
Contributor Author

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 {
Copy link
Contributor

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.
Copy link
Contributor

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>) {
Copy link
Contributor

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,
Copy link
Contributor

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>
@ggreenway ggreenway enabled auto-merge (squash) April 25, 2024 15:13
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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oneof is now discouraged:

* 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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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?

Copy link
Contributor Author

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;

Copy link
Contributor

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?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That SGTM, thanks @ggreenway !

Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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?

Copy link
Contributor Author

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>
@RyanTheOptimist
Copy link
Contributor

/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 {
Copy link
Contributor

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;
Copy link
Contributor

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

Copy link
Contributor Author

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;
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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?

Copy link
Contributor

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.

Copy link
Contributor

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?

Copy link
Contributor

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

Copy link
Contributor Author

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?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AddressFamilyConfig sounds better.

Copy link
Contributor Author

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>
Signed-off-by: Greg Greenway <ggreenway@apple.com>
abeyad
abeyad previously approved these changes May 1, 2024
Copy link
Contributor

@abeyad abeyad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/lgtm api

@repokitteh-read-only repokitteh-read-only bot removed the api label May 1, 2024
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>
Signed-off-by: Greg Greenway <ggreenway@apple.com>
@RyanTheOptimist
Copy link
Contributor

@danzh2010 does this LG to you?

Signed-off-by: Greg Greenway <ggreenway@apple.com>
/**
* 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
Copy link
Contributor

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?

Copy link
Contributor Author

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>
Copy link
Contributor

@danzh2010 danzh2010 left a 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!

Signed-off-by: Greg Greenway <ggreenway@apple.com>
Copy link
Contributor

@abeyad abeyad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/lgtm api

@repokitteh-read-only repokitteh-read-only bot removed the api label May 6, 2024
@ggreenway
Copy link
Contributor Author

@RyanTheOptimist please review

Copy link
Contributor

@RyanTheOptimist RyanTheOptimist left a 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!

@ggreenway ggreenway merged commit c940f24 into envoyproxy:main May 7, 2024
54 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants