[PATCH 2 of 2] The "sort=" parameter of the "resolver" directive
Maxim Dounin
mdounin at mdounin.ru
Tue Jul 12 16:19:43 UTC 2022
Hello!
On Tue, Jul 12, 2022 at 06:59:39PM +0400, Sergey Kandaurov wrote:
> > On 8 Jul 2022, at 04:35, Maxim Dounin <mdounin at mdounin.ru> wrote:
> >
> > On Thu, Jul 07, 2022 at 07:49:51PM +0400, Sergey Kandaurov wrote:
> >
> >>>> @@ -4250,7 +4269,27 @@ ngx_resolver_export(ngx_resolver_t *r, n
> >>>> }
> >>>>
> >>>> i = 0;
> >>>> - d = rotate ? ngx_random() % n : 0;
> >>>> +
> >>>> + switch (r->prefer) {
> >>>> +
> >>>> +#if (NGX_HAVE_INET6)
> >>>> + case NGX_RESOLVE_PREFER_A:
> >>>> + d = 0;
> >>>> + break;
> >>>> +
> >>>> + case NGX_RESOLVE_PREFER_AAAA:
> >>>> + d = rn->naddrs6;
> >>>> +
> >>>> + if (d == n) {
> >>>> + d = 0;
> >>>> + }
> >>>> +
> >>>> + break;
> >>>> +#endif
> >>>> +
> >>>> + default:
> >>>> + d = rotate ? ngx_random() % n : 0;
> >>>> + }
> >>>
> >>> With this code, a configuration like this:
> >>>
> >>> resolver ... prefer=ipv4;
> >>> set $foo "";
> >>> proxy_pass http://example.com$foo;
> >>>
> >>> will result in only IPv4 addresses being used assuming successful
> >>> connections, and IPv6 addresses being used only as a backup. This
> >>> looks quite different from the current behaviour, as well as from
> >>> what we do with
> >>>
> >>> proxy_pass http://example.com;
> >>>
> >>> when using system resolver.
> >>
> >> Can you please elaborate, what specific concerns are you referring to?
> >> The prefer option implements exactly the expected behaviour:
> >> first, a flat array is populated with preferred addresses
> >> (IPv4 for "prefer=ipv4", if any), then - with the rest, such as IPv6.
> >> The API user iterates though them until she gets a "successful" address.
> >>
> >> If the name is in the resolver cache, then rotation is also applied.
> >> The default nginx resolver behaviour is to rotate resolved addresses
> >> regardless of address families. Unlike this, in case of "prefer=ipv4",
> >> addresses are rotated within address families, that is, AFs are sorted:
> >> ipv4_x, ipv4_y, ipv4_z; ipv6_x, ipv6_y, ipv6_z
> >>
> >> This is close to how system resolver is used with getaddrinfo(), which
> >> depends on a preference and, if applicable, AF/address reachability.
> >
> > Try the two above configurations with a name which resolves to
> > 127.0.0.1 and ::1, and with both addresses responding on port 80.
> > Configuration without variables (using system resolver) will
> > balance requests between both addresses, regardless of system
> > resolver settings. Configuration with variables and resolver with
> > "prefer=ipv4" will use only the IPv4 address.
> >
> > server {
> > listen localhost:8080;
> >
> > location /dynamic/ {
> > resolver 8.8.8.8 prefer=ipv4;
> > set $foo "";
> > proxy_pass http://test.mdounin.ru:8081$foo;
> > }
> >
> > location /static/ {
> > proxy_pass http://test.mdounin.ru:8082;
> > }
> > }
> >
> > server {
> > listen test.mdounin.ru:8081;
> > listen test.mdounin.ru:8082;
> > return 200 $server_addr\n;
> > }
> >
> > Static configuration without variables uses both addresses:
> >
> > $ curl http://127.0.0.1:8080/static/
> > 127.0.0.1
> > $ curl http://127.0.0.1:8080/static/
> > ::1
> > $ curl http://127.0.0.1:8080/static/
> > 127.0.0.1
> > $ curl http://127.0.0.1:8080/static/
> > ::1
> >
> > Dynamic configuration with "prefer=ipv4" will only use IPv4 (IPv6
> > addresses will be used only in case of errors):
> >
> > $ curl http://127.0.0.1:8080/dynamic/
> > 127.0.0.1
> > $ curl http://127.0.0.1:8080/dynamic/
> > 127.0.0.1
> > $ curl http://127.0.0.1:8080/dynamic/
> > 127.0.0.1
> > $ curl http://127.0.0.1:8080/dynamic/
> > 127.0.0.1
> >
> > [...]
>
> Thanks for clarification.
>
> >
> >>> Not sure we want to introduce such behaviour. While it might be
> >>> closer to what RFC 6724 recommends for clients, it is clearly not
> >>> in line with how we handle multiple upstream addresses in general,
> >>> and certainly will confuse users. If we want to introduce this,
> >>> it probably should be at least consistent within resolver vs.
> >>> system resolver cases.
> >>
> >> If you refer to how we balance though multiple addresses in upstream
> >> implicitly defined with proxy_pass vs. proxy_pass with variable, then
> >> I tend to agree with you. In implicitly defined upstream, addresses
> >> are selected with rr balancer, which eventually makes them tried all.
> >> OTOH, the prefer option used for proxy_pass with variable, factually
> >> moves the unprefer addresses to backup, and since the upstream group
> >> isn't preserved across requests, this makes them almost never tried.
> >> But this is how proxy_pass with variable is used to work.
> >
> > Yes, I refer to the difference in handling of multiple upstream
> > addresses which is introduced with this change. Right now there
> > are no difference in the behaviour of static proxy_pass (without
> > variables) and dynamic one (with variables). With "prefer=ipv4"
> > as implemented the difference appears, and this certainly breaks
> > POLA.
> >
> > One possible option would be to change "prefer=" to rotate all
> > addresses, so proxy_pass will try them all. With this approach,
> > "prefer=ipv4" would be exactly equivalent to the default behaviour
> > (on the first resolution, resolver returns list of all IPv4
> > addresses, followed by all IPv6 addresses, and then addresses are
> > rotated) and "prefer=ipv6" would use the reverse order on the
> > first resolution (IPv6 followed by IPv4). Not sure it is at all
> > needed though (but might be still beneficial for tasks like OCSP
> > server resolution).
>
> Updated patch to rotate regardless of preference.
> Below is hg diff -w on top off of the previous one, for clarity:
>
> diff -r d9a8c2d87055 src/core/ngx_resolver.c
> --- a/src/core/ngx_resolver.c Wed Jul 06 17:10:24 2022 +0400
> +++ b/src/core/ngx_resolver.c Tue Jul 12 18:55:56 2022 +0400
> @@ -4270,6 +4270,10 @@ ngx_resolver_export(ngx_resolver_t *r, n
>
> i = 0;
>
> + if (rotate) {
> + d = ngx_random() % n;
> +
> + } else {
> switch (r->prefer) {
>
> #if (NGX_HAVE_INET6)
> @@ -4288,7 +4292,8 @@ ngx_resolver_export(ngx_resolver_t *r, n
> #endif
>
> default:
> - d = rotate ? ngx_random() % n : 0;
> + d = 0;
> + }
> }
>
> if (rn->naddrs) {
>
> Personally, I think such patch has a little sense. Keeping preference
> is reasonable e.g. to avoid connections over certain address family,
> should they be slow/tunneled or otherwise uneven, which I believe is
> quite common in practice. Such way, they would be always tried as a
> last resort and indeed can be seen as a backup option.
> In that sense, I'm rather skeptical about rotation behaviour in static
> proxy_pass (by round-robin means) that doesn't distinguish address families
> and apparently is a legacy from times (4d68c486fcb0) before URLs obtained
> IPv6 support in eaf95350d75c.
> Changing it requires more investment though and certainly breaks POLA.
> OTOH, with "ipv4=" introduction, such unpreferred connections can be
> simply disabled.
I generally agree. For now, the best option seems to postpone the
"prefer=" patch.
[...]
> i = 0;
> - d = rotate ? ngx_random() % n : 0;
> +
> + if (rotate) {
> + d = ngx_random() % n;
> +
> + } else {
> + switch (r->prefer) {
> +
> +#if (NGX_HAVE_INET6)
> + case NGX_RESOLVE_PREFER_A:
> + d = 0;
> + break;
> +
> + case NGX_RESOLVE_PREFER_AAAA:
> + d = rn->naddrs6;
> +
> + if (d == n) {
> + d = 0;
> + }
> +
> + break;
> +#endif
> +
> + default:
> + d = 0;
Just a side note: in this form, NGX_RESOLVE_PREFER_A is exactly
equivalent to no preference, so r->prefer can be represented with
just one bit.
--
Maxim Dounin
http://mdounin.ru/
More information about the nginx-devel
mailing list