Skip to content

reverseproxy: move DNS SRV+A discovery into modules/internal/network#7790

Open
tannevaled wants to merge 4 commits into
caddyserver:masterfrom
tannevaled:feat/dedup-dynamic-upstreams
Open

reverseproxy: move DNS SRV+A discovery into modules/internal/network#7790
tannevaled wants to merge 4 commits into
caddyserver:masterfrom
tannevaled:feat/dedup-dynamic-upstreams

Conversation

@tannevaled

@tannevaled tannevaled commented Jun 2, 2026

Copy link
Copy Markdown

What

Reverse proxy's dynamic-upstream DNS discovery (SRV and A/AAAA resolution, with caching/refresh/grace) was duplicated between SRVUpstreams and AUpstreams, and is the kind of logic third-party proxies want to reuse. This extracts it into a single shared place and refactors reverse_proxy to consume it (behavior unchanged).

Placement (per review)

Following @francislavoie's feedback, this is not a top-level package — it's internal helper code, not part of Caddy's runtime surface. It now lives in the existing modules/internal/network package, alongside the other shared network helper there. reverse_proxy's upstreams.go calls network.SRV(...) / network.A(...).

Because it's under modules/internal, third-party modules (e.g. layer4) can't import it yet, so they keep their own copy for now; they can switch over once layer4 is part of Caddy's standard distribution.

Changes

  • modules/internal/network/dynamicupstreams.go: transport-neutral Target + SRV(...) / A(...) with the existing caching/refresh/grace semantics, plus ResetSRV / ResetAllSRV.
  • modules/caddyhttp/reverseproxy/upstreams.go: SRVUpstreams/AUpstreams delegate to the shared functions (−177 lines of duplicated logic).
  • Tests for the shared package (resolve/cache/grace/reset, cache bounding, filtered-record handling, concurrent-refresh dedup under -race).

No change to reverse_proxy's Caddyfile/JSON surface or behavior.

@CLAassistant

CLAassistant commented Jun 2, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@tannevaled tannevaled force-pushed the feat/dedup-dynamic-upstreams branch from 9eb4ac3 to b93be96 Compare June 2, 2026 13:52
Move the SRV and A/AAAA resolution + caching logic out of reverseproxy
into a new, transport-neutral package (dynamicupstreams) that returns
neutral targets instead of *reverseproxy.Upstream. reverseproxy's
SRVUpstreams and AUpstreams now delegate to it and build their own
upstreams from the targets; behavior is unchanged.

This lets other proxies (e.g. third-party layer4 proxies) reuse the same
DNS discovery + caching instead of copying it, reducing duplication and
maintenance burden. reverseproxy/upstreams.go shrinks substantially.

RFC for the de-duplication discussed in caddyserver/caddy-l4#429.
@tannevaled tannevaled changed the title reverseproxy: extract DNS SRV discovery into a reusable package (RFC) reverseproxy: extract DNS SRV+A discovery into a reusable package (RFC) Jun 2, 2026
@tannevaled tannevaled force-pushed the feat/dedup-dynamic-upstreams branch from b93be96 to 5d83b60 Compare June 2, 2026 13:53
@tannevaled tannevaled marked this pull request as ready for review June 2, 2026 15:54
…und, concurrent refresh

Brings the package to 96.8% statement coverage. Adds:
- TestResetAllSRV: full-cache drop
- TestSRVFilteredRecords: LookupSRV partial-error semantics (usable
  records returned, error downgraded to a warning)
- TestSRVCacheBound / TestACacheBound: eviction keeps the cache bounded
  at maxCacheEntries
- TestSRVConcurrentRefreshDeduplicates / TestAConcurrentRefreshDeduplicates:
  two goroutines missing the read-lock fast path trigger only one lookup
  (verified under -race)

The grace-period and filtered-records logs now use a level-enabled logger
so the log-write branches execute. The only uncovered statements are the
two double-checked-locking re-check returns, which are reachable solely in
a narrow race window (the same untested idiom as reverseproxy's SRV/A).
@francislavoie

francislavoie commented Jun 14, 2026

Copy link
Copy Markdown
Member

I don't think it should be a top-level package/directory, it has nothing to do with Caddy as a runtime specifically. We typically put things in modules/ but it's not really a module in of itself, instead moreso being some shared library code.

I would say this would end up becoming a package in internal long-term, with the assumption that layer4 ends up being moved into Caddy's standard distribution, which would make it allowed to use internal.

So I'm thinking we can probably just put it in modules/internal/network for now, it's a useful refactor, but layer4 won't be able to use this until later, so it'll need to use a copy for the time being.

tannevaled and others added 2 commits June 19, 2026 00:21
checkout v4→v7, setup-go v5→v6, cache→v5, upload-artifact→v7,
download-artifact→v8. Clears the deprecated Node.js 20 action runtime;
all downloads are by name/pattern (no artifact-ids), so the v5 by-ID
path change does not apply.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per review feedback, the shared discovery code does not belong in a
top-level package: it is internal helper code, not part of Caddy's
runtime surface. Move it from the top-level dynamicupstreams package into
the existing modules/internal/network package and update reverse_proxy's
upstreams.go to consume it from there.

Being under modules/internal means third-party modules (e.g. layer4)
cannot import it yet; they keep their own copy until layer4 is part of
Caddy's standard distribution, at which point they can switch over.
@tannevaled tannevaled changed the title reverseproxy: extract DNS SRV+A discovery into a reusable package (RFC) reverseproxy: move DNS SRV+A discovery into modules/internal/network Jun 29, 2026
@tannevaled

Copy link
Copy Markdown
Author

Thanks, that placement makes sense. Done: I moved it out of the top-level package into the existing modules/internal/network package, and reverse_proxy's upstreams.go now calls network.SRV(...) / network.A(...). Title/description updated to match.

Agreed that being under modules/internal means layer4 can't import it yet, so it keeps its copy for the time being — switching it over once layer4 is part of the standard distribution. So this PR is now purely the in-tree dedup of reverse_proxy's SRV/A discovery (behavior unchanged, −177 lines), with tests for the shared package. No rush on review.

@vnxme

vnxme commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

So I'm thinking we can probably just put it in modules/internal/network for now, it's a useful refactor, but layer4 won't be able to use this until later, so it'll need to use a copy for the time being.

@francislavoie, is it possible we find an exportable place in the mainline code, so that the layer4 app could easily reuse this code before it gets merged? Same as you, I suggest we find a solution to avoid duplicating things.

@tannevaled

Copy link
Copy Markdown
Author

Happy to go whichever way you two prefer — it's a one-line move for me.

One option that threads both concerns: keep it under modules/ (not a top-level package, per @francislavoie) but exported rather than internal — i.e. modules/network instead of modules/internal/network. That lets the layer4 app import it and drop its copy today, while not adding anything to Caddy's top-level surface. The tradeoff is that it does become part of the importable API a bit earlier than the "wait until layer4 is in-tree" plan.

If you'd rather keep it internal for now, that's fine too and I'll leave it as-is. Just let me know where you land and I'll adjust the package path (and wire caddy-l4 to it if we go exported).

@francislavoie

Copy link
Copy Markdown
Member

Thing is, it's not really a module, so that's my reticence here.

I'd like to see l4 moved into Caddy sooner rather than later but I'm not actively maintaining this project

@vnxme

vnxme commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

I don't insist on moving the code into a new module either. Do you mind if we simply make some relevant types and functions exportable and reusable while keeping them within the reverseproxy module?

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.

4 participants