@marcel91200 / caddy Mirror · read-only / commits / 941eae5f
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 13, 05:30 UTC

reverseproxy: allow specifying ip version for dynamic `a` upstream (#5401)

Co-authored-by: Francis Lavoie <lavofr@gmail.com>

Emily Lange committed Feb 27, 2023 at 18:23 UTC 941eae5f615aeaf038f62002e673a7bf4886f1c7
3 files changed +64 -5
caddytest/integration/caddyfile_adapt/reverse_proxy_dynamic_upstreams.txt
+6 -2
@@ -11,6 +11,7 @@
11 resolvers 8.8.8.8 8.8.4.4
12 dial_timeout 2s
13 dial_fallback_delay 300ms
14 + versions ipv6
15 }
16 }
17 }
@@ -66,7 +67,10 @@
67 "8.8.4.4"
68 ]
69 },
69 - "source": "a"
70 + "source": "a",
71 + "versions": {
72 + "ipv6": true
73 + }
74 },
75 "handler": "reverse_proxy"
76 }
@@ -113,4 +117,4 @@
117 }
118 }
119 }
116 -}
\ No newline at end of file
120 +}
modules/caddyhttp/reverseproxy/caddyfile.go
+24 -1
@@ -1324,6 +1324,7 @@ func (u *SRVUpstreams) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
1324 // resolvers <resolvers...>
1325 // dial_timeout <timeout>
1326 // dial_fallback_delay <timeout>
1327 +// versions ipv4|ipv6
1328 // }
1329 func (u *AUpstreams) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
1330 for d.Next() {
@@ -1397,8 +1398,30 @@ func (u *AUpstreams) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
1398 }
1399 u.FallbackDelay = caddy.Duration(dur)
1400
1401 + case "versions":
1402 + args := d.RemainingArgs()
1403 + if len(args) == 0 {
1404 + return d.Errf("must specify at least one version")
1405 + }
1406 +
1407 + if u.Versions == nil {
1408 + u.Versions = &ipVersions{}
1409 + }
1410 +
1411 + trueBool := true
1412 + for _, arg := range args {
1413 + switch arg {
1414 + case "ipv4":
1415 + u.Versions.IPv4 = &trueBool
1416 + case "ipv6":
1417 + u.Versions.IPv6 = &trueBool
1418 + default:
1419 + return d.Errf("unsupported version: '%s'", arg)
1420 + }
1421 + }
1422 +
1423 default:
1401 - return d.Errf("unrecognized srv option '%s'", d.Val())
1424 + return d.Errf("unrecognized a option '%s'", d.Val())
1425 }
1426 }
1427 }
modules/caddyhttp/reverseproxy/upstreams.go
+34 -2
@@ -213,6 +213,11 @@ func (sl srvLookup) isFresh() bool {
213 return time.Since(sl.freshness) < time.Duration(sl.srvUpstreams.Refresh)
214 }
215
216 +type ipVersions struct {
217 + IPv4 *bool `json:"ipv4,omitempty"`
218 + IPv6 *bool `json:"ipv6,omitempty"`
219 +}
220 +
221 // AUpstreams provides upstreams from A/AAAA lookups.
222 // Results are cached and refreshed at the configured
223 // refresh interval.
@@ -240,6 +245,11 @@ type AUpstreams struct {
245 // A negative value disables this.
246 FallbackDelay caddy.Duration `json:"dial_fallback_delay,omitempty"`
247
248 + // The IP versions to resolve for. By default, both
249 + // "ipv4" and "ipv6" will be enabled, which
250 + // correspond to A and AAAA records respectively.
251 + Versions *ipVersions `json:"versions,omitempty"`
252 +
253 resolver *net.Resolver
254 }
255
@@ -286,7 +296,29 @@ func (au *AUpstreams) Provision(_ caddy.Context) error {
296
297 func (au AUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, error) {
298 repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
289 - auStr := repl.ReplaceAll(au.String(), "")
299 +
300 + resolveIpv4 := au.Versions.IPv4 == nil || *au.Versions.IPv4
301 + resolveIpv6 := au.Versions.IPv6 == nil || *au.Versions.IPv6
302 +
303 + // Map ipVersion early, so we can use it as part of the cache-key.
304 + // This should be fairly inexpensive and comes and the upside of
305 + // allowing the same dynamic upstream (name + port combination)
306 + // to be used multiple times with different ip versions.
307 + //
308 + // It also forced a cache-miss if a previously cached dynamic
309 + // upstream changes its ip version, e.g. after a config reload,
310 + // while keeping the cache-invalidation as simple as it currently is.
311 + var ipVersion string
312 + switch {
313 + case resolveIpv4 && !resolveIpv6:
314 + ipVersion = "ip4"
315 + case !resolveIpv4 && resolveIpv6:
316 + ipVersion = "ip6"
317 + default:
318 + ipVersion = "ip"
319 + }
320 +
321 + auStr := repl.ReplaceAll(au.String()+ipVersion, "")
322
323 // first, use a cheap read-lock to return a cached result quickly
324 aAaaaMu.RLock()
@@ -311,7 +343,7 @@ func (au AUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, error) {
343 name := repl.ReplaceAll(au.Name, "")
344 port := repl.ReplaceAll(au.Port, "")
345
314 - ips, err := au.resolver.LookupIPAddr(r.Context(), name)
346 + ips, err := au.resolver.LookupIP(r.Context(), ipVersion, name)
347 if err != nil {
348 return nil, err
349 }