@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 177 lines 6.96 KB
Raw
1 // Copyright 2015 Matthew Holt and The Caddy Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 // Package network provides transport-neutral DNS-based discovery of upstream
16 // targets, with result caching. It lives under modules/internal so it can be
17 // shared between Caddy's own proxies (currently the HTTP reverse_proxy) without
18 // each copying the resolution and caching logic.
19 //
20 // The package intentionally returns neutral [Target] values rather than any
21 // proxy-specific upstream type; each caller builds its own upstream
22 // representation from the targets.
23 package network
24
25 import (
26 "context"
27 "net"
28 "strconv"
29 "time"
30
31 "go.uber.org/zap"
32 "go.uber.org/zap/zapcore"
33 )
34
35 // Target is a single discovered upstream endpoint. It carries the SRV priority
36 // and weight when available (both zero for non-SRV lookups).
37 type Target struct {
38 Host string
39 Port string
40 Priority uint16
41 Weight uint16
42 }
43
44 // SRVLookupFunc resolves SRV records. It matches the signature of
45 // (*net.Resolver).LookupSRV, so callers can pass that directly (and tests can
46 // inject a stub).
47 type SRVLookupFunc func(ctx context.Context, service, proto, name string) (cname string, addrs []*net.SRV, err error)
48
49 // SRV resolves the given SRV record into targets, caching the result for
50 // refresh. lookup is the resolver to use (typically (*net.Resolver).LookupSRV).
51 // resolverKey identifies that resolver, so results looked up with different
52 // resolvers for the same record are cached separately; pass "" for the default
53 // resolver.
54 //
55 // If the lookup fails but returns some records (e.g. a few invalid names were
56 // filtered out), those records are still used. If it fails with no records and
57 // grace > 0, the previously cached result keeps being served for up to grace
58 // past its refresh instead of returning an error. With no cached result, grace
59 // also suppresses the error and returns no targets, preserving the reverse
60 // proxy's existing behavior.
61 //
62 // Concurrent calls for the same record share one lookup, and no lock is held
63 // while it runs, so a slow lookup does not delay calls for other records. The
64 // shared lookup is detached from ctx (and bounded by its own timeout), so the
65 // caller that started it can go away without failing it for the others; ctx
66 // still bounds how long this call waits for the result.
67 func SRV(ctx context.Context, lookup SRVLookupFunc, resolverKey, service, proto, name string, refresh, grace time.Duration, logger *zap.Logger) ([]Target, error) {
68 key := srvKey(resolverKey, service, proto, name)
69 return srvCache.get(ctx, key, refresh, grace, logger, func(ctx context.Context) ([]Target, error) {
70 if c := logger.Check(zapcore.DebugLevel, "refreshing SRV upstreams"); c != nil {
71 c.Write(zap.String("service", service), zap.String("proto", proto), zap.String("name", name))
72 }
73
74 _, records, err := lookup(ctx, service, proto, name)
75 if err != nil {
76 // From LookupSRV docs: invalid names are filtered out and an error is
77 // returned alongside any remaining results; only treat it as fatal when
78 // nothing usable came back.
79 if len(records) == 0 {
80 return nil, err
81 }
82 if c := logger.Check(zapcore.WarnLevel, "SRV records filtered"); c != nil {
83 c.Write(zap.Error(err))
84 }
85 }
86
87 targets := make([]Target, len(records))
88 for i, rec := range records {
89 if c := logger.Check(zapcore.DebugLevel, "discovered SRV record"); c != nil {
90 c.Write(zap.String("target", rec.Target), zap.Uint16("port", rec.Port),
91 zap.Uint16("priority", rec.Priority), zap.Uint16("weight", rec.Weight))
92 }
93 targets[i] = Target{
94 Host: rec.Target,
95 Port: strconv.Itoa(int(rec.Port)),
96 Priority: rec.Priority,
97 Weight: rec.Weight,
98 }
99 }
100 return targets, nil
101 })
102 }
103
104 // ResetSRV removes the cached result for a single SRV record resolved with the
105 // given resolver (see [SRV] for resolverKey). A lookup for it that is already
106 // in flight will not write its result back.
107 func ResetSRV(resolverKey, service, proto, name string) {
108 srvCache.reset(srvKey(resolverKey, service, proto, name))
109 }
110
111 // ResetAllSRV clears the entire SRV cache. Lookups already in flight will not
112 // write their results back.
113 func ResetAllSRV() {
114 srvCache.resetAll()
115 }
116
117 func srvKey(resolverKey, service, proto, name string) string {
118 return resolverKey + "\x00" + service + "\x00" + proto + "\x00" + name
119 }
120
121 // IPLookupFunc resolves a host's IP addresses. It matches the signature of
122 // (*net.Resolver).LookupIP, so callers can pass that directly (and tests can
123 // inject a stub). network is one of "ip", "ip4", "ip6".
124 type IPLookupFunc func(ctx context.Context, network, host string) ([]net.IP, error)
125
126 // A resolves name's A/AAAA records into targets (one per address, all using the
127 // given port), caching the result for refresh. network selects the IP versions
128 // ("ip", "ip4" or "ip6"). lookup is the resolver to use (typically
129 // (*net.Resolver).LookupIP). resolverKey identifies that resolver, so results
130 // looked up with different resolvers for the same name are cached separately;
131 // pass "" for the default resolver.
132 //
133 // Concurrency and cancellation behave as described for [SRV].
134 func A(ctx context.Context, lookup IPLookupFunc, resolverKey, network, name, port string, refresh time.Duration, logger *zap.Logger) ([]Target, error) {
135 key := aKey(resolverKey, network, name, port)
136 return aCache.get(ctx, key, refresh, 0, logger, func(ctx context.Context) ([]Target, error) {
137 if c := logger.Check(zapcore.DebugLevel, "refreshing A upstreams"); c != nil {
138 c.Write(zap.String("version", network), zap.String("name", name), zap.String("port", port))
139 }
140
141 ips, err := lookup(ctx, network, name)
142 if err != nil {
143 return nil, err
144 }
145
146 targets := make([]Target, len(ips))
147 for i, ip := range ips {
148 if c := logger.Check(zapcore.DebugLevel, "discovered A record"); c != nil {
149 c.Write(zap.String("ip", ip.String()))
150 }
151 targets[i] = Target{Host: ip.String(), Port: port}
152 }
153 return targets, nil
154 })
155 }
156
157 // ResetA removes the cached result for a single A/AAAA lookup (see [A] for the
158 // parameters). A lookup for it that is already in flight will not write its
159 // result back.
160 func ResetA(resolverKey, network, name, port string) {
161 aCache.reset(aKey(resolverKey, network, name, port))
162 }
163
164 // ResetAllA clears the entire A/AAAA cache. Lookups already in flight will not
165 // write their results back.
166 func ResetAllA() {
167 aCache.resetAll()
168 }
169
170 func aKey(resolverKey, network, name, port string) string {
171 return resolverKey + "\x00" + name + "\x00" + port + "\x00" + network
172 }
173
174 var (
175 srvCache = newCache("SRV")
176 aCache = newCache("A")
177 )