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
}