@marcel91200 / caddy Mirror · read-only
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 11, 01:00 UTC

master
go 471 lines 14.7 KB
Raw
1 package reverseproxy
2
3 import (
4 "context"
5 "encoding/json"
6 "fmt"
7 weakrand "math/rand/v2"
8 "net"
9 "net/http"
10 "slices"
11 "strconv"
12 "strings"
13 "time"
14
15 "go.uber.org/zap"
16 "go.uber.org/zap/zapcore"
17
18 "github.com/caddyserver/caddy/v2"
19 "github.com/caddyserver/caddy/v2/modules/internal/network"
20 )
21
22 func init() {
23 caddy.RegisterModule(SRVUpstreams{})
24 caddy.RegisterModule(AUpstreams{})
25 caddy.RegisterModule(MultiUpstreams{})
26 }
27
28 // SRVUpstreams provides upstreams from SRV lookups.
29 // The lookup DNS name can be configured either by
30 // its individual parts (that is, specifying the
31 // service, protocol, and name separately) to form
32 // the standard "_service._proto.name" domain, or
33 // the domain can be specified directly in name by
34 // leaving service and proto empty. See RFC 2782.
35 //
36 // Lookups are cached and refreshed at the configured
37 // refresh interval.
38 //
39 // Returned upstreams are sorted by priority and weight.
40 type SRVUpstreams struct {
41 // The service label.
42 Service string `json:"service,omitempty"`
43
44 // The protocol label; either tcp or udp.
45 Proto string `json:"proto,omitempty"`
46
47 // The name label; or, if service and proto are
48 // empty, the entire domain name to look up.
49 Name string `json:"name,omitempty"`
50
51 // The interval at which to refresh the SRV lookup.
52 // Results are cached between lookups. Default: 1m
53 Refresh caddy.Duration `json:"refresh,omitempty"`
54
55 // If > 0 and there is an error with the lookup,
56 // continue to use the cached results for up to
57 // this long before trying again, (even though they
58 // are stale) instead of returning an error to the
59 // client. Default: 0s.
60 GracePeriod caddy.Duration `json:"grace_period,omitempty"`
61
62 // Configures the DNS resolver used to resolve the
63 // SRV address to SRV records.
64 Resolver *UpstreamResolver `json:"resolver,omitempty"`
65
66 // If Resolver is configured, how long to wait before
67 // timing out trying to connect to the DNS server.
68 DialTimeout caddy.Duration `json:"dial_timeout,omitempty"`
69
70 // If Resolver is configured, how long to wait before
71 // spawning an RFC 6555 Fast Fallback connection.
72 // A negative value disables this.
73 FallbackDelay caddy.Duration `json:"dial_fallback_delay,omitempty"`
74
75 // Specific network to dial when connecting to the upstream(s)
76 // provided by SRV records upstream. See Go's net package for
77 // accepted values. For example, to restrict to IPv4, use "tcp4".
78 DialNetwork string `json:"dial_network,omitempty"`
79
80 resolver *net.Resolver
81
82 // resolverKey identifies resolver for the shared DNS cache, so two SRV
83 // sources that look up the same record with different resolvers do not
84 // share a cache entry. Empty for the default resolver.
85 resolverKey string
86
87 logger *zap.Logger
88 }
89
90 // CaddyModule returns the Caddy module information.
91 func (SRVUpstreams) CaddyModule() caddy.ModuleInfo {
92 return caddy.ModuleInfo{
93 ID: "http.reverse_proxy.upstreams.srv",
94 New: func() caddy.Module { return new(SRVUpstreams) },
95 }
96 }
97
98 func (su *SRVUpstreams) Provision(ctx caddy.Context) error {
99 su.logger = ctx.Logger()
100 if su.Refresh == 0 {
101 su.Refresh = caddy.Duration(time.Minute)
102 }
103
104 if su.Resolver != nil {
105 err := su.Resolver.ParseAddresses()
106 if err != nil {
107 return err
108 }
109 d := &net.Dialer{
110 Timeout: time.Duration(su.DialTimeout),
111 FallbackDelay: time.Duration(su.FallbackDelay),
112 }
113 su.resolver = &net.Resolver{
114 PreferGo: true,
115 Dial: func(ctx context.Context, _, _ string) (net.Conn, error) {
116 //nolint:gosec
117 addr := su.Resolver.netAddrs[weakrand.IntN(len(su.Resolver.netAddrs))]
118 return d.DialContext(ctx, addr.Network, addr.JoinHostPort(0))
119 },
120 }
121 }
122 if su.resolver == nil {
123 su.resolver = net.DefaultResolver
124 }
125 su.resolverKey = resolverCacheKey(su.Resolver)
126
127 return nil
128 }
129
130 func (su *SRVUpstreams) ResetCache(r *http.Request) error {
131 if r == nil {
132 network.ResetAllSRV()
133 return nil
134 }
135 _, service, proto, name := su.expandedAddr(r)
136 network.ResetSRV(su.resolverKey, service, proto, name)
137 return nil
138 }
139
140 func (su SRVUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, error) {
141 _, service, proto, name := su.expandedAddr(r)
142
143 targets, err := network.SRV(r.Context(), su.resolver.LookupSRV,
144 su.resolverKey, service, proto, name,
145 time.Duration(su.Refresh), time.Duration(su.GracePeriod), su.logger)
146 if err != nil {
147 return nil, err
148 }
149
150 upstreams := make([]*Upstream, len(targets))
151 for i, t := range targets {
152 addr := net.JoinHostPort(t.Host, t.Port)
153 if su.DialNetwork != "" {
154 addr = su.DialNetwork + "/" + addr
155 }
156 upstreams[i] = &Upstream{Dial: addr}
157 }
158 return upstreams, nil
159 }
160
161 func (su SRVUpstreams) String() string {
162 if su.Service == "" && su.Proto == "" {
163 return su.Name
164 }
165 return su.formattedAddr(su.Service, su.Proto, su.Name)
166 }
167
168 // expandedAddr expands placeholders in the configured SRV domain labels.
169 // The return values are: addr, the RFC 2782 representation of the SRV domain;
170 // service, the service; proto, the protocol; and name, the name.
171 // If su.Service and su.Proto are empty, name will be returned as addr instead.
172 func (su SRVUpstreams) expandedAddr(r *http.Request) (addr, service, proto, name string) {
173 repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
174 name = repl.ReplaceAll(su.Name, "")
175 if su.Service == "" && su.Proto == "" {
176 addr = name
177 return addr, service, proto, name
178 }
179 service = repl.ReplaceAll(su.Service, "")
180 proto = repl.ReplaceAll(su.Proto, "")
181 addr = su.formattedAddr(service, proto, name)
182 return addr, service, proto, name
183 }
184
185 // formattedAddr the RFC 2782 representation of the SRV domain, in
186 // the form "_service._proto.name".
187 func (SRVUpstreams) formattedAddr(service, proto, name string) string {
188 return fmt.Sprintf("_%s._%s.%s", service, proto, name)
189 }
190
191 type IPVersions struct {
192 IPv4 *bool `json:"ipv4,omitempty"`
193 IPv6 *bool `json:"ipv6,omitempty"`
194 }
195
196 func resolveIpVersion(versions *IPVersions) string {
197 resolveIpv4 := versions == nil || (versions.IPv4 == nil && versions.IPv6 == nil) || (versions.IPv4 != nil && *versions.IPv4)
198 resolveIpv6 := versions == nil || (versions.IPv6 == nil && versions.IPv4 == nil) || (versions.IPv6 != nil && *versions.IPv6)
199 switch {
200 case resolveIpv4 && !resolveIpv6:
201 return "ip4"
202 case !resolveIpv4 && resolveIpv6:
203 return "ip6"
204 default:
205 return "ip"
206 }
207 }
208
209 // AUpstreams provides upstreams from A/AAAA lookups.
210 // Results are cached and refreshed at the configured
211 // refresh interval.
212 type AUpstreams struct {
213 // The domain name to look up.
214 Name string `json:"name,omitempty"`
215
216 // The port to use with the upstreams. Default: 80
217 Port string `json:"port,omitempty"`
218
219 // The interval at which to refresh the A lookup.
220 // Results are cached between lookups. Default: 1m
221 Refresh caddy.Duration `json:"refresh,omitempty"`
222
223 // Configures the DNS resolver used to resolve the
224 // domain name to A records.
225 Resolver *UpstreamResolver `json:"resolver,omitempty"`
226
227 // If Resolver is configured, how long to wait before
228 // timing out trying to connect to the DNS server.
229 DialTimeout caddy.Duration `json:"dial_timeout,omitempty"`
230
231 // If Resolver is configured, how long to wait before
232 // spawning an RFC 6555 Fast Fallback connection.
233 // A negative value disables this.
234 FallbackDelay caddy.Duration `json:"dial_fallback_delay,omitempty"`
235
236 // The IP versions to resolve for. By default, both
237 // "ipv4" and "ipv6" will be enabled, which
238 // correspond to A and AAAA records respectively.
239 Versions *IPVersions `json:"versions,omitempty"`
240
241 resolver *net.Resolver
242
243 // resolverKey identifies resolver for the shared DNS cache; see the same
244 // field on SRVUpstreams. Empty for the default resolver.
245 resolverKey string
246
247 logger *zap.Logger
248 }
249
250 // CaddyModule returns the Caddy module information.
251 func (AUpstreams) CaddyModule() caddy.ModuleInfo {
252 return caddy.ModuleInfo{
253 ID: "http.reverse_proxy.upstreams.a",
254 New: func() caddy.Module { return new(AUpstreams) },
255 }
256 }
257
258 func (au *AUpstreams) Provision(ctx caddy.Context) error {
259 au.logger = ctx.Logger()
260 if au.Refresh == 0 {
261 au.Refresh = caddy.Duration(time.Minute)
262 }
263 if au.Port == "" {
264 au.Port = "80"
265 }
266
267 if au.Resolver != nil {
268 err := au.Resolver.ParseAddresses()
269 if err != nil {
270 return err
271 }
272 d := &net.Dialer{
273 Timeout: time.Duration(au.DialTimeout),
274 FallbackDelay: time.Duration(au.FallbackDelay),
275 }
276 au.resolver = &net.Resolver{
277 PreferGo: true,
278 Dial: func(ctx context.Context, _, _ string) (net.Conn, error) {
279 //nolint:gosec
280 addr := au.Resolver.netAddrs[weakrand.IntN(len(au.Resolver.netAddrs))]
281 return d.DialContext(ctx, addr.Network, addr.JoinHostPort(0))
282 },
283 }
284 }
285 if au.resolver == nil {
286 au.resolver = net.DefaultResolver
287 }
288 au.resolverKey = resolverCacheKey(au.Resolver)
289
290 return nil
291 }
292
293 // ResetCache clears the cached lookup for the given request, or the entire
294 // A/AAAA cache if r is nil.
295 func (au *AUpstreams) ResetCache(r *http.Request) error {
296 if r == nil {
297 network.ResetAllA()
298 return nil
299 }
300 ipVersion, name, port := au.expandedAddr(r)
301 network.ResetA(au.resolverKey, ipVersion, name, port)
302 return nil
303 }
304
305 func (au AUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, error) {
306 ipVersion, name, port := au.expandedAddr(r)
307
308 targets, err := network.A(r.Context(), au.resolver.LookupIP,
309 au.resolverKey, ipVersion, name, port, time.Duration(au.Refresh), au.logger)
310 if err != nil {
311 return nil, err
312 }
313
314 upstreams := make([]*Upstream, len(targets))
315 for i, t := range targets {
316 upstreams[i] = &Upstream{Dial: net.JoinHostPort(t.Host, t.Port)}
317 }
318 return upstreams, nil
319 }
320
321 func (au AUpstreams) String() string { return net.JoinHostPort(au.Name, au.Port) }
322
323 // expandedAddr returns the IP version to resolve and the placeholder-expanded
324 // name and port, which together identify the request's cache entry.
325 func (au AUpstreams) expandedAddr(r *http.Request) (ipVersion, name, port string) {
326 repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
327 return resolveIpVersion(au.Versions), repl.ReplaceAll(au.Name, ""), repl.ReplaceAll(au.Port, "")
328 }
329
330 // MultiUpstreams is a single dynamic upstream source that
331 // aggregates the results of multiple dynamic upstream sources.
332 // All configured sources will be queried in order, with their
333 // results appended to the end of the list. Errors returned
334 // from individual sources will be logged and the next source
335 // will continue to be invoked.
336 //
337 // This module makes it easy to implement redundant cluster
338 // failovers, especially in conjunction with the `first` load
339 // balancing policy: if the first source returns an error or
340 // no upstreams, the second source's upstreams will be used
341 // naturally.
342 type MultiUpstreams struct {
343 // The list of upstream source modules to get upstreams from.
344 // They will be queried in order, with their results appended
345 // in the order they are returned.
346 SourcesRaw []json.RawMessage `json:"sources,omitempty" caddy:"namespace=http.reverse_proxy.upstreams inline_key=source"`
347 sources []UpstreamSource
348
349 logger *zap.Logger
350 }
351
352 // CaddyModule returns the Caddy module information.
353 func (MultiUpstreams) CaddyModule() caddy.ModuleInfo {
354 return caddy.ModuleInfo{
355 ID: "http.reverse_proxy.upstreams.multi",
356 New: func() caddy.Module { return new(MultiUpstreams) },
357 }
358 }
359
360 func (mu *MultiUpstreams) Provision(ctx caddy.Context) error {
361 mu.logger = ctx.Logger()
362
363 if mu.SourcesRaw != nil {
364 mod, err := ctx.LoadModule(mu, "SourcesRaw")
365 if err != nil {
366 return fmt.Errorf("loading upstream source modules: %v", err)
367 }
368 sources := mod.([]any)
369 mu.sources = make([]UpstreamSource, 0, len(sources))
370 for _, src := range sources {
371 mu.sources = append(mu.sources, src.(UpstreamSource))
372 }
373 }
374
375 return nil
376 }
377
378 func (mu MultiUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, error) {
379 var upstreams []*Upstream
380
381 for i, src := range mu.sources {
382 select {
383 case <-r.Context().Done():
384 return upstreams, context.Canceled
385 default:
386 }
387
388 up, err := src.GetUpstreams(r)
389 if err != nil {
390 if c := mu.logger.Check(zapcore.ErrorLevel, "upstream source returned error"); c != nil {
391 c.Write(
392 zap.Int("source_idx", i),
393 zap.Error(err),
394 )
395 }
396 } else if len(up) == 0 {
397 if c := mu.logger.Check(zapcore.WarnLevel, "upstream source returned 0 upstreams"); c != nil {
398 c.Write(zap.Int("source_idx", i))
399 }
400 } else {
401 upstreams = append(upstreams, up...)
402 }
403 }
404
405 return upstreams, nil
406 }
407
408 // UpstreamResolver holds the set of addresses of DNS resolvers of
409 // upstream addresses
410 type UpstreamResolver struct {
411 // The addresses of DNS resolvers to use when looking up the addresses of proxy upstreams.
412 // It accepts [network addresses](/docs/conventions#network-addresses)
413 // with port range of only 1. If the host is an IP address, it will be dialed directly to resolve the upstream server.
414 // If the host is not an IP address, the addresses are resolved using the [name resolution convention](https://golang.org/pkg/net/#hdr-Name_Resolution) of the Go standard library.
415 // If the array contains more than 1 resolver address, one is chosen at random.
416 Addresses []string `json:"addresses,omitempty"`
417 netAddrs []caddy.NetworkAddress
418 }
419
420 // ParseAddresses parses all the configured network addresses
421 // and ensures they're ready to be used.
422 func (u *UpstreamResolver) ParseAddresses() error {
423 u.netAddrs = make([]caddy.NetworkAddress, 0, len(u.Addresses))
424 for _, v := range u.Addresses {
425 addr, err := caddy.ParseNetworkAddressWithDefaults(v, "udp", 53)
426 if err != nil {
427 return err
428 }
429 if addr.PortRangeSize() != 1 {
430 return fmt.Errorf("resolver address must have exactly one address; cannot call %v", addr)
431 }
432 u.netAddrs = append(u.netAddrs, addr)
433 }
434 return nil
435 }
436
437 // resolverCacheKey returns a stable identity for the configured resolver, used
438 // to key the shared DNS cache so that two upstream sources looking up the same
439 // name with different resolvers do not share a cache entry. The default
440 // resolver (no Resolver configured) yields "". Addresses are sorted so that the
441 // same set in a different order maps to the same key.
442 //
443 // Each address is length-prefixed ("<len>:<addr>") rather than joined with a
444 // separator, so the encoding is unambiguous even when an address itself
445 // contains the separator — e.g. a Unix-socket resolver path with a comma.
446 // Without this, {"a,b"} and {"a", "b"} would collide, defeating the very
447 // isolation this key provides.
448 func resolverCacheKey(r *UpstreamResolver) string {
449 if r == nil || len(r.Addresses) == 0 {
450 return ""
451 }
452 addrs := slices.Clone(r.Addresses)
453 slices.Sort(addrs)
454 var b strings.Builder
455 for _, a := range addrs {
456 b.WriteString(strconv.Itoa(len(a)))
457 b.WriteByte(':')
458 b.WriteString(a)
459 }
460 return b.String()
461 }
462
463 // Interface guards
464 var (
465 _ caddy.Provisioner = (*SRVUpstreams)(nil)
466 _ UpstreamSource = (*SRVUpstreams)(nil)
467 _ CachingUpstreamSource = (*SRVUpstreams)(nil)
468 _ caddy.Provisioner = (*AUpstreams)(nil)
469 _ UpstreamSource = (*AUpstreams)(nil)
470 _ CachingUpstreamSource = (*AUpstreams)(nil)
471 )