@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 359 lines 11.6 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 reverseproxy
16
17 import (
18 "context"
19 "fmt"
20 "net/netip"
21 "strconv"
22 "sync"
23 "sync/atomic"
24 "time"
25
26 "github.com/caddyserver/caddy/v2"
27 )
28
29 // UpstreamPool is a collection of upstreams.
30 type UpstreamPool []*Upstream
31
32 // Upstream bridges this proxy's configuration to the
33 // state of the backend host it is correlated with.
34 // Upstream values must not be copied.
35 type Upstream struct {
36 *Host `json:"-"`
37
38 // The [network address](/docs/conventions#network-addresses)
39 // to dial to connect to the upstream. Must represent precisely
40 // one socket (i.e. no port ranges). A valid network address
41 // either has a host and port or is a unix socket address.
42 //
43 // Placeholders may be used to make the upstream dynamic, but be
44 // aware of the health check implications of this: a single
45 // upstream that represents numerous (perhaps arbitrary) backends
46 // can be considered down if one or enough of the arbitrary
47 // backends is down. Also be aware of open proxy vulnerabilities.
48 Dial string `json:"dial,omitempty"`
49
50 // The maximum number of simultaneous requests to allow to
51 // this upstream. If set, overrides the global passive health
52 // check UnhealthyRequestCount value.
53 MaxRequests int `json:"max_requests,omitempty"`
54
55 // TODO: This could be really useful, to bind requests
56 // with certain properties to specific backends
57 // HeaderAffinity string
58 // IPAffinity string
59
60 activeHealthCheckPort int
61 activeHealthCheckUpstream string
62 healthCheckPolicy *PassiveHealthChecks
63 cb CircuitBreaker
64
65 // state from the active health checker. It lives here rather than on
66 // the shared Host because the Host is keyed by dial address alone,
67 // while an active health check is configured per handler: two handlers
68 // dialing the same address with different health_uri or health_headers
69 // are checking distinct health targets, and must not push each other
70 // over their own consecutive pass/fail thresholds.
71 unhealthy atomic.Int32
72 activePasses atomic.Int64
73 activeFails atomic.Int64
74 }
75
76 // (pointer receiver necessary to avoid a race condition, since
77 // copying the Upstream reads the 'unhealthy' field which is
78 // accessed atomically)
79 func (u *Upstream) String() string { return u.Dial }
80
81 // Available returns true if the remote host
82 // is available to receive requests. This is
83 // the method that should be used by selection
84 // policies, etc. to determine if a backend
85 // should be able to be sent a request.
86 func (u *Upstream) Available() bool {
87 return u.Healthy() && !u.Full()
88 }
89
90 // Healthy returns true if the remote host
91 // is currently known to be healthy or "up".
92 // It consults the circuit breaker, if any.
93 func (u *Upstream) Healthy() bool {
94 healthy := u.healthy()
95 if healthy && u.healthCheckPolicy != nil {
96 healthy = u.Host.Fails() < u.healthCheckPolicy.MaxFails
97 }
98 if healthy && u.cb != nil {
99 healthy = u.cb.OK()
100 }
101 return healthy
102 }
103
104 // Full returns true if the remote host
105 // cannot receive more requests at this time.
106 func (u *Upstream) Full() bool {
107 return u.MaxRequests > 0 && u.Host.NumRequests() >= u.MaxRequests
108 }
109
110 // fillDialInfo returns a filled DialInfo for upstream u, using the request
111 // context. Note that the returned value is not a pointer.
112 func (u *Upstream) fillDialInfo(repl *caddy.Replacer) (DialInfo, error) {
113 var addr caddy.NetworkAddress
114
115 // use provided dial address
116 var err error
117 dial := repl.ReplaceAll(u.Dial, "")
118 addr, err = caddy.ParseNetworkAddress(dial)
119 if err != nil {
120 return DialInfo{}, fmt.Errorf("upstream %s: invalid dial address %s: %v", u.Dial, dial, err)
121 }
122 if numPorts := addr.PortRangeSize(); numPorts != 1 {
123 return DialInfo{}, fmt.Errorf("upstream %s: dial address must represent precisely one socket: %s represents %d",
124 u.Dial, dial, numPorts)
125 }
126
127 return DialInfo{
128 Upstream: u,
129 Network: addr.Network,
130 Address: addr.JoinHostPort(0),
131 Host: addr.Host,
132 Port: strconv.Itoa(int(addr.StartPort)),
133 }, nil
134 }
135
136 func (u *Upstream) fillHost() {
137 host := new(Host)
138 existingHost, loaded := hosts.LoadOrStore(u.String(), host)
139 if loaded {
140 host = existingHost.(*Host)
141 }
142 u.Host = host
143 }
144
145 // fillDynamicHost is like fillHost, but stores the host in the separate
146 // dynamicHosts map rather than the reference-counted UsagePool. Dynamic
147 // hosts are not reference-counted; instead, they are retained as long as
148 // they are actively seen and are evicted by a background cleanup goroutine
149 // after dynamicHostIdleExpiry of inactivity. This preserves health state
150 // (e.g. passive fail counts) across sequential requests.
151 func (u *Upstream) fillDynamicHost() {
152 dynamicHostsMu.Lock()
153 entry, ok := dynamicHosts[u.String()]
154 if ok {
155 entry.lastSeen = time.Now()
156 dynamicHosts[u.String()] = entry
157 u.Host = entry.host
158 } else {
159 h := new(Host)
160 dynamicHosts[u.String()] = dynamicHostEntry{host: h, lastSeen: time.Now()}
161 u.Host = h
162 }
163 dynamicHostsMu.Unlock()
164
165 // ensure the cleanup goroutine is running
166 dynamicHostsCleanerOnce.Do(func() {
167 go func() {
168 for {
169 time.Sleep(dynamicHostCleanupInterval)
170 dynamicHostsMu.Lock()
171 for addr, entry := range dynamicHosts {
172 if time.Since(entry.lastSeen) > dynamicHostIdleExpiry {
173 delete(dynamicHosts, addr)
174 }
175 }
176 dynamicHostsMu.Unlock()
177 }
178 }()
179 })
180 }
181
182 // Host is the basic, in-memory representation of the state of a remote host.
183 // Its fields are accessed atomically and Host values must not be copied.
184 type Host struct {
185 numRequests atomic.Int64
186 fails atomic.Int64
187 }
188
189 // NumRequests returns the number of active requests to the upstream.
190 func (h *Host) NumRequests() int {
191 return int(h.numRequests.Load())
192 }
193
194 // Fails returns the number of recent failures with the upstream.
195 func (h *Host) Fails() int {
196 return int(h.fails.Load())
197 }
198
199 // countRequest mutates the active request count by
200 // delta. It returns an error if the adjustment fails.
201 func (h *Host) countRequest(delta int) error {
202 result := h.numRequests.Add(int64(delta))
203 if result < 0 {
204 return fmt.Errorf("count below 0: %d", result)
205 }
206 return nil
207 }
208
209 // countFail mutates the recent failures count by
210 // delta. It returns an error if the adjustment fails.
211 func (h *Host) countFail(delta int) error {
212 result := h.fails.Add(int64(delta))
213 if result < 0 {
214 return fmt.Errorf("count below 0: %d", result)
215 }
216 return nil
217 }
218
219 // activeHealthPasses returns the number of consecutive passing
220 // active health checks observed by this upstream's checker.
221 func (u *Upstream) activeHealthPasses() int {
222 return int(u.activePasses.Load())
223 }
224
225 // activeHealthFails returns the number of consecutive failing
226 // active health checks observed by this upstream's checker.
227 func (u *Upstream) activeHealthFails() int {
228 return int(u.activeFails.Load())
229 }
230
231 // countHealthPass mutates the recent passes count by
232 // delta. It returns an error if the adjustment fails.
233 func (u *Upstream) countHealthPass(delta int) error {
234 result := u.activePasses.Add(int64(delta))
235 if result < 0 {
236 return fmt.Errorf("count below 0: %d", result)
237 }
238 return nil
239 }
240
241 // countHealthFail mutates the recent failures count by
242 // delta. It returns an error if the adjustment fails.
243 func (u *Upstream) countHealthFail(delta int) error {
244 result := u.activeFails.Add(int64(delta))
245 if result < 0 {
246 return fmt.Errorf("count below 0: %d", result)
247 }
248 return nil
249 }
250
251 // resetHealth resets the health check counters.
252 func (u *Upstream) resetHealth() {
253 u.activePasses.Store(0)
254 u.activeFails.Store(0)
255 }
256
257 // healthy returns true if the upstream is not actively marked as unhealthy.
258 // (This returns the status only from the "active" health checks.)
259 func (u *Upstream) healthy() bool {
260 return u.unhealthy.Load() == 0
261 }
262
263 // SetHealthy sets the upstream has healthy or unhealthy
264 // and returns true if the new value is different. This
265 // sets the status only for the "active" health checks.
266 func (u *Upstream) setHealthy(healthy bool) bool {
267 var unhealthy, compare int32 = 1, 0
268 if healthy {
269 unhealthy, compare = 0, 1
270 }
271 return u.unhealthy.CompareAndSwap(compare, unhealthy)
272 }
273
274 // DialInfo contains information needed to dial a
275 // connection to an upstream host. This information
276 // may be different than that which is represented
277 // in a URL (for example, unix sockets don't have
278 // a host that can be represented in a URL, but
279 // they certainly have a network name and address).
280 type DialInfo struct {
281 // Upstream is the Upstream associated with
282 // this DialInfo. It may be nil.
283 Upstream *Upstream
284
285 // The network to use. This should be one of
286 // the values that is accepted by net.Dial:
287 // https://golang.org/pkg/net/#Dial
288 Network string
289
290 // The address to dial. Follows the same
291 // semantics and rules as net.Dial.
292 Address string
293
294 // Host and Port are components of Address.
295 Host, Port string
296 }
297
298 // String returns the Caddy network address form
299 // by joining the network and address with a
300 // forward slash.
301 func (di DialInfo) String() string {
302 return caddy.JoinNetworkAddress(di.Network, di.Host, di.Port)
303 }
304
305 // GetDialInfo gets the upstream dialing info out of the context,
306 // and returns true if there was a valid value; false otherwise.
307 func GetDialInfo(ctx context.Context) (DialInfo, bool) {
308 dialInfo, ok := ctx.Value(dialInfoCtxKey).(DialInfo)
309 return dialInfo, ok
310 }
311
312 // hosts is the global repository for hosts that are
313 // currently in use by active configuration(s). This
314 // allows the state of remote hosts to be preserved
315 // through config reloads.
316 var hosts = caddy.NewUsagePool()
317
318 // dynamicHosts tracks hosts that were provisioned from dynamic upstream
319 // sources. Unlike static upstreams which are reference-counted via the
320 // UsagePool, dynamic upstream hosts are not reference-counted. Instead,
321 // their last-seen time is updated on each request, and a background
322 // goroutine evicts entries that have been idle for dynamicHostIdleExpiry.
323 // This preserves health state (e.g. passive fail counts) across requests
324 // to the same dynamic backend.
325 var (
326 dynamicHosts = make(map[string]dynamicHostEntry)
327 dynamicHostsMu sync.RWMutex
328 dynamicHostsCleanerOnce sync.Once
329 dynamicHostCleanupInterval = 5 * time.Minute
330 dynamicHostIdleExpiry = time.Hour
331 )
332
333 // dynamicHostEntry holds a Host and the last time it was seen
334 // in a set of dynamic upstreams returned for a request.
335 type dynamicHostEntry struct {
336 host *Host
337 lastSeen time.Time
338 }
339
340 // dialInfoCtxKey is the context key used for the variable that holds
341 // the dial info for the upstream connection.
342 const dialInfoCtxKey caddy.CtxKey = "reverse_proxy.dial_info"
343
344 // proxyProtocolInfoVarKey is the key used for the variable that holds
345 // the proxy protocol info for the upstream connection.
346 const proxyProtocolInfoVarKey = "reverse_proxy.proxy_protocol_info"
347
348 // ProxyProtocolInfo contains information needed to write proxy protocol to a
349 // connection to an upstream host.
350 type ProxyProtocolInfo struct {
351 AddrPort netip.AddrPort
352 }
353
354 // tlsH1OnlyVarKey is the key used that indicates the connection will use h1 only for TLS.
355 // https://github.com/caddyserver/caddy/issues/7292
356 const tlsH1OnlyVarKey = "reverse_proxy.tls_h1_only"
357
358 // proxyVarKey is the key used that indicates the proxy server used for a request.
359 const proxyVarKey = "reverse_proxy.proxy"