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

Proxy address reuse and expiry #367

Closed
wants to merge 19 commits into from

Conversation

ylavic
Copy link
Member

@ylavic ylavic commented Jul 5, 2023

No description provided.

@ylavic ylavic force-pushed the proxy_address_reuse_and_expiry branch 2 times, most recently from ebf11e3 to e474096 Compare July 5, 2023 13:39
@ylavic ylavic force-pushed the proxy_address_reuse_and_expiry branch from e474096 to 0d3792b Compare July 5, 2023 14:34
modules/proxy/mod_proxy.h Show resolved Hide resolved
modules/proxy/mod_proxy_ftp.c Outdated Show resolved Hide resolved
modules/proxy/mod_proxy.c Show resolved Hide resolved
modules/proxy/mod_proxy_balancer.c Show resolved Hide resolved
modules/proxy/proxy_util.c Outdated Show resolved Hide resolved
modules/proxy/proxy_util.c Show resolved Hide resolved
s, APLOGNO(00960) "%s: an error occurred creating a "
"new connection to %pI (%s)", proxy_function,
backend_addr, conn->hostname);
/* XXX: Will be closed when proxy_conn is closed */
socket_cleanup(conn);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why isn't this needed any longer?

Copy link
Member Author

@ylavic ylavic Jul 10, 2023

Choose a reason for hiding this comment

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

Every caller of ap_proxy_connection_create[_ex]() should release/invalidate the connection on failure already, like with any other ap_proxy_ function taking a proxy_conn_rec which fails? We do this upstream already afaict.

Copy link
Contributor

Choose a reason for hiding this comment

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

Good point, but in order to be save shouldn't we do at least a conn->close = 1 here in case the caller only releases the connection but forgets to update the close flag before? But I agree that the current consumers in our code do it properly.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added 97ce1e9 to be consistent on how we close the socket on reuse failure.

modules/proxy/proxy_util.c Outdated Show resolved Hide resolved
modules/proxy/proxy_util.c Outdated Show resolved Hide resolved

/* Update worker->cp->addr if it's not set (or reset) */
if (!AP_VOLATILIZE_T(apr_sockaddr_t *, worker->cp->addr)) {
worker->cp->addr = address->addr;
Copy link
Contributor

Choose a reason for hiding this comment

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

How do we ensure it never expires even if it got reset in the meantime?

Copy link
Member Author

Choose a reason for hiding this comment

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

The first address/DNS lookup will never expire because it has a refcount of 2, but if some (third party) module plays by resetting worker->cp->addr arbitrarily after the startup it will be set to the current address, which will expire when a new one is needed and the worker or last connection using it releases it.
I don't think it's ever been safe to reset worker->cp->addr, so we should be good?

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree that using setting worker->cp->addr to NULL as an indicator was not a good idea and given that this possibility is probably only available in one patch release of 2.4. I am personally happy to drop this. I only feel unsure if we could do it for formal reasons. But probably as long as no one complains we get away with it :-p.

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't see where we do this in 2.4, it's trunk changes only for now and addressed by this PR so we should be good ;)

Copy link
Contributor

Choose a reason for hiding this comment

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

My bad. Missed that it was never part of 2.4.x.

modules/proxy/proxy_util.c Outdated Show resolved Hide resolved
asfgit pushed a commit that referenced this pull request Jul 21, 2023
@rpluem
Copy link
Contributor

rpluem commented Aug 23, 2023

Let's go for it.

@rpluem
Copy link
Contributor

rpluem commented Aug 28, 2023

@ylavic : Do you care to commit to trunk?

1 similar comment
@rpluem
Copy link
Contributor

rpluem commented Sep 19, 2023

@ylavic : Do you care to commit to trunk?

@ylavic
Copy link
Member Author

ylavic commented Sep 20, 2023

Do you care to commit to trunk?

Yeah, sorry about the delay (quite busy times..).
I just pushed some more commits for what I could find (re-)reading the changes a few months later, the plan is to squash the many commits in bigger related ones with better commit messages (I'll do this locally) and then push that to trunk. Hopefully tonight..

to check if the address changed only, and if not return APR_EEXIST.
If it changed update the worker address as usual for every one to
know/update.

This is used by ap_proxy_connect_backend() to check if it should try
with some new address(es) on failure to connect() first with the current
one(s), and avoid expiring the worker address globally/spuriously for
connect() errors potentially not related to expired address(es).
@ylavic
Copy link
Member Author

ylavic commented Sep 21, 2023

The last commit (176de10) is to avoid expiring the current backend address forcibly and maybe spuriously if ap_proxy_connect_backend() fails on the existing address(es) for something else than some address(es) change. WDYT?

@asfgit asfgit closed this in 3c7f67f Sep 21, 2023
@ylavic ylavic deleted the proxy_address_reuse_and_expiry branch September 21, 2023 13:16
rpluem pushed a commit to rpluem/httpd that referenced this pull request Oct 30, 2023
Define a new proxy_address struct holding the current/latest sockaddr in use
by each proxy worker and conn. Since backend addresses can be updated when
their TTL expires and while connections are being processed, each address is
refcounted and freed only when the last worker (or conn) using it grabs the
new one.

The lifetime of the addresses is handled at a single place by the new
ap_proxy_determine_address() function. It guarantees to bind the current/latest
backend address to the passed in conn (or do nothing if it's up to date already).
The function is called indirectly by ap_proxy_determine_connection() for the
proxy modules that use it, or directly by mod_proxy_ftp and mod_proxy_hcheck.
It also is called eventually by ap_proxy_connect_backend() when connect()ing all
the current addresses fails, to check (PROXY_DETERMINE_ADDRESS_CHECK) if some
new addrs are available.

This commit is also a rework of the lifetime of conn->addr, conn->hostname
and conn->forward, using the conn->uds_pool and conn->fwd_pool for the cases
where the backend is connected through a UDS socket and a remote CONNECT proxy
respectively.

* include/ap_mmn.h:
  Minor bump for new function/fields.

* modules/proxy/mod_proxy.h (struct proxy_address,
                             ap_proxy_determine_addresss()):
  Declare ap_proxy_determine_addresss() and opaque struct proxy_address,
  new fields to structs proxy_conn_rec/proxy_worker_shared/proxy_worker.

* modules/proxy/mod_proxy.c (set_worker_param):
  Parse/set the new worker->address_ttl parameter.

* modules/proxy/proxy_util.c (proxy_util_register_hooks(),
                              ap_proxy_initialize_worker(),
                              ap_proxy_connection_reusable(),
                              ap_proxyerror(), proxyerror_core(),
                              init_conn_pool(), make_conn_subpool(),
                              connection_make(), connection_cleanup(),
                              connection_constructor()):
 Initialize *proxy_start_time in proxy_util_register_hooks() as the epoch
 from which expiration times are relative (i.e. seconds stored in an uint32_t
 for atomic changes).
 Make sure worker->s->is_address_reusable and worker->s->disablereuse are
 consistant in ap_proxy_initialize_worker(), thus no need to check for both
 in ap_proxy_connection_reusable().
 New proxyerror_core() helper taking an apr_status_t to log, wrap in
 ap_proxyerror().
 New make_conn_subpool() to create worker->cp->{pool,dns} with their own
 allocator.
 New connection_make() helper to factorize code in connection_cleanup() and
 connection_constructor().

* modules/proxy/proxy_util.c (proxy_address_inc(), proxy_address_dec(),
                              proxy_address_cleanup(), proxy_address_set_expired(),
                              worker_address_get(), worker_address_set(),
                              worker_address_resolve(), proxy_addrs_equal(),
                              ap_proxy_determine_address(),
                              ap_proxy_determine_connection(),
                              ap_proxy_connect_backend()):
 Implement ap_proxy_determine_address() using the above helpers for atomic changes,
 and call it from ap_proxy_determine_connection() and ap_proxy_connect_backend().

* modules/proxy/mod_proxy_ftp.c (proxy_ftp_handler):
  Use ap_proxy_determine_address() and use the returned backend->addr.

* modules/proxy/mod_proxy_hcheck.c (hc_determine_connection, hc_get_backend,
                                    hc_init_worker, hc_watchdog_callback):
  Use ap_proxy_determine_address() in hc_determine_connection() and call the
  latter from hc_get_backend(), replace hc_init_worker() by hc_init_baton()
  which now calls hc_get_hcworker() and hc_get_backend() to resolve the first
  address at init time.

* modules/proxy/mod_proxy_http.c (proxy_http_handler):
  Use backend->addr and ->hostname instead of worker->cp->addr and
  worker->s->hostname_ex respectively.

* modules/proxy/mod_proxy_ajp.c (ap_proxy_ajp_request):
  Use backend->addr and ->hostname instead of worker->cp->addr and
  worker->s->hostname_ex respectively.

Closes apache#367

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1912459 13f79535-47bb-0310-9956-ffa450edef68
(cherry picked from commit 3c7f67f)
rpluem pushed a commit to rpluem/httpd that referenced this pull request Oct 30, 2023
Define a new proxy_address struct holding the current/latest sockaddr in use
by each proxy worker and conn. Since backend addresses can be updated when
their TTL expires and while connections are being processed, each address is
refcounted and freed only when the last worker (or conn) using it grabs the
new one.

The lifetime of the addresses is handled at a single place by the new
ap_proxy_determine_address() function. It guarantees to bind the current/latest
backend address to the passed in conn (or do nothing if it's up to date already).
The function is called indirectly by ap_proxy_determine_connection() for the
proxy modules that use it, or directly by mod_proxy_ftp and mod_proxy_hcheck.
It also is called eventually by ap_proxy_connect_backend() when connect()ing all
the current addresses fails, to check (PROXY_DETERMINE_ADDRESS_CHECK) if some
new addrs are available.

This commit is also a rework of the lifetime of conn->addr, conn->hostname
and conn->forward, using the conn->uds_pool and conn->fwd_pool for the cases
where the backend is connected through a UDS socket and a remote CONNECT proxy
respectively.

* include/ap_mmn.h:
  Minor bump for new function/fields.

* modules/proxy/mod_proxy.h (struct proxy_address,
                             ap_proxy_determine_addresss()):
  Declare ap_proxy_determine_addresss() and opaque struct proxy_address,
  new fields to structs proxy_conn_rec/proxy_worker_shared/proxy_worker.

* modules/proxy/mod_proxy.c (set_worker_param):
  Parse/set the new worker->address_ttl parameter.

* modules/proxy/proxy_util.c (proxy_util_register_hooks(),
                              ap_proxy_initialize_worker(),
                              ap_proxy_connection_reusable(),
                              ap_proxyerror(), proxyerror_core(),
                              init_conn_pool(), make_conn_subpool(),
                              connection_make(), connection_cleanup(),
                              connection_constructor()):
 Initialize *proxy_start_time in proxy_util_register_hooks() as the epoch
 from which expiration times are relative (i.e. seconds stored in an uint32_t
 for atomic changes).
 Make sure worker->s->is_address_reusable and worker->s->disablereuse are
 consistant in ap_proxy_initialize_worker(), thus no need to check for both
 in ap_proxy_connection_reusable().
 New proxyerror_core() helper taking an apr_status_t to log, wrap in
 ap_proxyerror().
 New make_conn_subpool() to create worker->cp->{pool,dns} with their own
 allocator.
 New connection_make() helper to factorize code in connection_cleanup() and
 connection_constructor().

* modules/proxy/proxy_util.c (proxy_address_inc(), proxy_address_dec(),
                              proxy_address_cleanup(), proxy_address_set_expired(),
                              worker_address_get(), worker_address_set(),
                              worker_address_resolve(), proxy_addrs_equal(),
                              ap_proxy_determine_address(),
                              ap_proxy_determine_connection(),
                              ap_proxy_connect_backend()):
 Implement ap_proxy_determine_address() using the above helpers for atomic changes,
 and call it from ap_proxy_determine_connection() and ap_proxy_connect_backend().

* modules/proxy/mod_proxy_ftp.c (proxy_ftp_handler):
  Use ap_proxy_determine_address() and use the returned backend->addr.

* modules/proxy/mod_proxy_hcheck.c (hc_determine_connection, hc_get_backend,
                                    hc_init_worker, hc_watchdog_callback):
  Use ap_proxy_determine_address() in hc_determine_connection() and call the
  latter from hc_get_backend(), replace hc_init_worker() by hc_init_baton()
  which now calls hc_get_hcworker() and hc_get_backend() to resolve the first
  address at init time.

* modules/proxy/mod_proxy_http.c (proxy_http_handler):
  Use backend->addr and ->hostname instead of worker->cp->addr and
  worker->s->hostname_ex respectively.

* modules/proxy/mod_proxy_ajp.c (ap_proxy_ajp_request):
  Use backend->addr and ->hostname instead of worker->cp->addr and
  worker->s->hostname_ex respectively.

Closes apache#367

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1912459 13f79535-47bb-0310-9956-ffa450edef68
(cherry picked from commit 3c7f67f)
rpluem pushed a commit to rpluem/httpd that referenced this pull request Oct 30, 2023
Define a new proxy_address struct holding the current/latest sockaddr in use
by each proxy worker and conn. Since backend addresses can be updated when
their TTL expires and while connections are being processed, each address is
refcounted and freed only when the last worker (or conn) using it grabs the
new one.

The lifetime of the addresses is handled at a single place by the new
ap_proxy_determine_address() function. It guarantees to bind the current/latest
backend address to the passed in conn (or do nothing if it's up to date already).
The function is called indirectly by ap_proxy_determine_connection() for the
proxy modules that use it, or directly by mod_proxy_ftp and mod_proxy_hcheck.
It also is called eventually by ap_proxy_connect_backend() when connect()ing all
the current addresses fails, to check (PROXY_DETERMINE_ADDRESS_CHECK) if some
new addrs are available.

This commit is also a rework of the lifetime of conn->addr, conn->hostname
and conn->forward, using the conn->uds_pool and conn->fwd_pool for the cases
where the backend is connected through a UDS socket and a remote CONNECT proxy
respectively.

* include/ap_mmn.h:
  Minor bump for new function/fields.

* modules/proxy/mod_proxy.h (struct proxy_address,
                             ap_proxy_determine_addresss()):
  Declare ap_proxy_determine_addresss() and opaque struct proxy_address,
  new fields to structs proxy_conn_rec/proxy_worker_shared/proxy_worker.

* modules/proxy/mod_proxy.c (set_worker_param):
  Parse/set the new worker->address_ttl parameter.

* modules/proxy/proxy_util.c (proxy_util_register_hooks(),
                              ap_proxy_initialize_worker(),
                              ap_proxy_connection_reusable(),
                              ap_proxyerror(), proxyerror_core(),
                              init_conn_pool(), make_conn_subpool(),
                              connection_make(), connection_cleanup(),
                              connection_constructor()):
 Initialize *proxy_start_time in proxy_util_register_hooks() as the epoch
 from which expiration times are relative (i.e. seconds stored in an uint32_t
 for atomic changes).
 Make sure worker->s->is_address_reusable and worker->s->disablereuse are
 consistant in ap_proxy_initialize_worker(), thus no need to check for both
 in ap_proxy_connection_reusable().
 New proxyerror_core() helper taking an apr_status_t to log, wrap in
 ap_proxyerror().
 New make_conn_subpool() to create worker->cp->{pool,dns} with their own
 allocator.
 New connection_make() helper to factorize code in connection_cleanup() and
 connection_constructor().

* modules/proxy/proxy_util.c (proxy_address_inc(), proxy_address_dec(),
                              proxy_address_cleanup(), proxy_address_set_expired(),
                              worker_address_get(), worker_address_set(),
                              worker_address_resolve(), proxy_addrs_equal(),
                              ap_proxy_determine_address(),
                              ap_proxy_determine_connection(),
                              ap_proxy_connect_backend()):
 Implement ap_proxy_determine_address() using the above helpers for atomic changes,
 and call it from ap_proxy_determine_connection() and ap_proxy_connect_backend().

* modules/proxy/mod_proxy_ftp.c (proxy_ftp_handler):
  Use ap_proxy_determine_address() and use the returned backend->addr.

* modules/proxy/mod_proxy_hcheck.c (hc_determine_connection, hc_get_backend,
                                    hc_init_worker, hc_watchdog_callback):
  Use ap_proxy_determine_address() in hc_determine_connection() and call the
  latter from hc_get_backend(), replace hc_init_worker() by hc_init_baton()
  which now calls hc_get_hcworker() and hc_get_backend() to resolve the first
  address at init time.

* modules/proxy/mod_proxy_http.c (proxy_http_handler):
  Use backend->addr and ->hostname instead of worker->cp->addr and
  worker->s->hostname_ex respectively.

* modules/proxy/mod_proxy_ajp.c (ap_proxy_ajp_request):
  Use backend->addr and ->hostname instead of worker->cp->addr and
  worker->s->hostname_ex respectively.

Closes apache#367

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1912459 13f79535-47bb-0310-9956-ffa450edef68
(cherry picked from commit 3c7f67f)
@darmbrust
Copy link

Is there an easy way to figure out what version this patch landed in? I see the docs for it are already advertised, but the docs don't say when it became a valid option. I don't see it mentioned in the changelog.

Also, if I'm understanding this feature correctly, it will be a better solution to a problem folks have where load balancers rotate IPs:
https://serverfault.com/questions/870026/apache-honouring-the-dns-ttl-in-proxy-pass
The current suggestion is to disablereuse=On

Does this section of the workers doc need to be updated with this feature?
https://httpd.apache.org/docs/2.4/mod/mod_proxy.html#workers

Specifically:

**
DNS resolution for origin domains

DNS resolution happens when the socket to the origin domain is created for the first time. When connection reuse is enabled, each backend domain is resolved only once per child process, and cached for all further connections until the child is recycled. This information should to be considered while planning DNS maintenance tasks involving backend domains. Please also check ProxyPass parameters for more details about connection reuse.
**

How does the above interact with this new feature? Will it remove connections when at age address_ttl? Or, will it take a failure on a connection, and then resolve dns if address_ttl is expired before creating a new connection?

@rpluem
Copy link
Contributor

rpluem commented Mar 19, 2024

Is there an easy way to figure out what version this patch landed in? I see the docs for it are already advertised, but the docs don't say when it became a valid option. I don't see it mentioned in the changelog.

It will be part of the next 2.4.x release.

Also, if I'm understanding this feature correctly, it will be a better solution to a problem folks have where load balancers rotate IPs: https://serverfault.com/questions/870026/apache-honouring-the-dns-ttl-in-proxy-pass The current suggestion is to disablereuse=On

Does this section of the workers doc need to be updated with this feature? https://httpd.apache.org/docs/2.4/mod/mod_proxy.html#workers

Specifically:

** DNS resolution for origin domains

DNS resolution happens when the socket to the origin domain is created for the first time. When connection reuse is enabled, each backend domain is resolved only once per child process, and cached for all further connections until the child is recycled. This information should to be considered while planning DNS maintenance tasks involving backend domains. Please also check ProxyPass parameters for more details about connection reuse. **

This would need some update.

How does the above interact with this new feature? Will it remove connections when at age address_ttl? Or, will it take a failure on a connection, and then resolve dns if address_ttl is expired before creating a new connection?

It will close connections when the TTL of the DNS entry is expired or if the the address changed in the meantime as another connection triggered a new lookup which resulted in a different address and recreate them.

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.

3 participants