@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 10, 22:30 UTC

master
go 314 lines 9.41 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 //go:build !unix || solaris
16
17 package caddy
18
19 import (
20 "context"
21 "fmt"
22 "net"
23 "os"
24 "slices"
25 "strconv"
26 "sync"
27 "sync/atomic"
28 "time"
29
30 "go.uber.org/zap"
31 )
32
33 func listenReusable(ctx context.Context, lnKey string, network, address string, config net.ListenConfig) (any, error) {
34 var socketFile *os.File
35
36 fd := slices.Contains([]string{"fd", "fdgram"}, network)
37 if fd {
38 socketFd, err := strconv.ParseUint(address, 0, strconv.IntSize)
39 if err != nil {
40 return nil, fmt.Errorf("invalid file descriptor: %v", err)
41 }
42
43 func() {
44 socketFilesMu.Lock()
45 defer socketFilesMu.Unlock()
46
47 socketFdWide := uintptr(socketFd)
48 var ok bool
49
50 socketFile, ok = socketFiles[socketFdWide]
51
52 if !ok {
53 socketFile = os.NewFile(socketFdWide, lnKey)
54 if socketFile != nil {
55 socketFiles[socketFdWide] = socketFile
56 }
57 }
58 }()
59
60 if socketFile == nil {
61 return nil, fmt.Errorf("invalid socket file descriptor: %d", socketFd)
62 }
63 }
64
65 datagram := slices.Contains([]string{"udp", "udp4", "udp6", "unixgram", "fdgram"}, network)
66 if datagram {
67 sharedPc, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
68 var (
69 pc net.PacketConn
70 err error
71 )
72 if fd {
73 pc, err = net.FilePacketConn(socketFile)
74 } else {
75 pc, err = config.ListenPacket(ctx, network, address)
76 }
77 if err != nil {
78 return nil, err
79 }
80 return &sharedPacketConn{PacketConn: pc, key: lnKey}, nil
81 })
82 if err != nil {
83 return nil, err
84 }
85 return &fakeClosePacketConn{sharedPacketConn: sharedPc.(*sharedPacketConn)}, nil
86 }
87
88 sharedLn, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
89 var (
90 ln net.Listener
91 err error
92 )
93 if fd {
94 ln, err = net.FileListener(socketFile)
95 } else {
96 ln, err = config.Listen(ctx, network, address)
97 }
98 if err != nil {
99 return nil, err
100 }
101 return &sharedListener{Listener: ln, key: lnKey}, nil
102 })
103 if err != nil {
104 return nil, err
105 }
106
107 return &fakeCloseListener{sharedListener: sharedLn.(*sharedListener), keepAliveConfig: config.KeepAliveConfig}, nil
108 }
109
110 // fakeCloseListener is a private wrapper over a listener that
111 // is shared. The state of fakeCloseListener is not shared.
112 // This allows one user of a socket to "close" the listener
113 // while in reality the socket stays open for other users of
114 // the listener. In this way, servers become hot-swappable
115 // while the listener remains running. Listeners should be
116 // re-wrapped in a new fakeCloseListener each time the listener
117 // is reused. This type is atomic and values must not be copied.
118 type fakeCloseListener struct {
119 closed atomic.Bool
120 *sharedListener // embedded, so we also become a net.Listener
121 keepAliveConfig net.KeepAliveConfig
122 }
123
124 type canSetKeepAliveConfig interface {
125 SetKeepAliveConfig(config net.KeepAliveConfig) error
126 }
127
128 func (fcl *fakeCloseListener) Accept() (net.Conn, error) {
129 // if the listener is already "closed", return error
130 if fcl.closed.Load() {
131 return nil, fakeClosedErr(fcl)
132 }
133
134 // call underlying accept
135 conn, err := fcl.sharedListener.Accept()
136 if err == nil {
137 // if 0, do nothing, Go's default is already set
138 // and if the connection allows setting KeepAlive, set it
139 if tconn, ok := conn.(canSetKeepAliveConfig); ok && fcl.keepAliveConfig.Enable {
140 err = tconn.SetKeepAliveConfig(fcl.keepAliveConfig)
141 if err != nil {
142 Log().With(zap.String("server", fcl.sharedListener.key)).Warn("unable to set keepalive for new connection:", zap.Error(err))
143 }
144 }
145 return conn, nil
146 }
147
148 // since Accept() returned an error, it may be because our reference to
149 // the listener (this fakeCloseListener) may have been closed, i.e. the
150 // server is shutting down; in that case, we need to clear the deadline
151 // that we set when Close() was called, and return a non-temporary and
152 // non-timeout error value to the caller, masking the "true" error, so
153 // that server loops / goroutines won't retry, linger, and leak
154 if fcl.closed.Load() {
155 // we dereference the sharedListener explicitly even though it's embedded
156 // so that it's clear in the code that side-effects are shared with other
157 // users of this listener, not just our own reference to it; we also don't
158 // do anything with the error because all we could do is log it, but we
159 // explicitly assign it to nothing so we don't forget it's there if needed
160 _ = fcl.sharedListener.clearDeadline()
161
162 if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
163 return nil, fakeClosedErr(fcl)
164 }
165 } else if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
166 // still open: this timeout is a past deadline a sibling set in Close(),
167 // clear it so the server loop resumes instead of spinning on it
168 _ = fcl.sharedListener.clearDeadline()
169 }
170
171 return nil, err
172 }
173
174 // Close stops accepting new connections without closing the
175 // underlying listener. The underlying listener is only closed
176 // if the caller is the last known user of the socket.
177 func (fcl *fakeCloseListener) Close() error {
178 if fcl.closed.CompareAndSwap(false, true) {
179 // There are two ways I know of to get an Accept()
180 // function to return to the server loop that called
181 // it: close the listener, or set a deadline in the
182 // past. Obviously, we can't close the socket yet
183 // since others may be using it (hence this whole
184 // file). But we can set the deadline in the past,
185 // and this is kind of cheating, but it works, and
186 // it apparently even works on Windows.
187 _ = fcl.sharedListener.setDeadline()
188 _, _ = listenerPool.Delete(fcl.sharedListener.key)
189 }
190 return nil
191 }
192
193 // sharedListener is a wrapper over an underlying listener. The listener
194 // and the other fields on the struct are shared state that is synchronized,
195 // so sharedListener structs must never be copied (always use a pointer).
196 type sharedListener struct {
197 net.Listener
198 key string // uniquely identifies this listener
199 deadline bool // whether a deadline is currently set
200 deadlineMu sync.Mutex
201 }
202
203 func (sl *sharedListener) clearDeadline() error {
204 var err error
205 sl.deadlineMu.Lock()
206 if sl.deadline {
207 switch ln := sl.Listener.(type) {
208 case *net.TCPListener:
209 err = ln.SetDeadline(time.Time{})
210 }
211 sl.deadline = false
212 }
213 sl.deadlineMu.Unlock()
214 return err
215 }
216
217 func (sl *sharedListener) setDeadline() error {
218 timeInPast := time.Now().Add(-1 * time.Minute)
219 var err error
220 sl.deadlineMu.Lock()
221 if !sl.deadline {
222 switch ln := sl.Listener.(type) {
223 case *net.TCPListener:
224 err = ln.SetDeadline(timeInPast)
225 }
226 sl.deadline = true
227 }
228 sl.deadlineMu.Unlock()
229 return err
230 }
231
232 // Destruct is called by the UsagePool when the listener is
233 // finally not being used anymore. It closes the socket.
234 func (sl *sharedListener) Destruct() error {
235 return sl.Listener.Close()
236 }
237
238 // fakeClosePacketConn is like fakeCloseListener, but for PacketConns,
239 // or more specifically, *net.UDPConn
240 type fakeClosePacketConn struct {
241 closed atomic.Bool
242 *sharedPacketConn // embedded, so we also become a net.PacketConn; its key is used in Close
243 }
244
245 func (fcpc *fakeClosePacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
246 // if the listener is already "closed", return error
247 if fcpc.closed.Load() {
248 return 0, nil, &net.OpError{
249 Op: "readfrom",
250 Net: fcpc.LocalAddr().Network(),
251 Addr: fcpc.LocalAddr(),
252 Err: errFakeClosed,
253 }
254 }
255
256 // call underlying readfrom
257 n, addr, err = fcpc.sharedPacketConn.ReadFrom(p)
258 if err != nil {
259 // this server was stopped, so clear the deadline and let
260 // any new server continue reading; but we will exit
261 if fcpc.closed.Load() {
262 if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
263 if err = fcpc.SetReadDeadline(time.Time{}); err != nil {
264 return n, addr, err
265 }
266 }
267 }
268 return n, addr, err
269 }
270
271 return n, addr, err
272 }
273
274 // Close won't close the underlying socket unless there is no more reference, then listenerPool will close it.
275 func (fcpc *fakeClosePacketConn) Close() error {
276 if fcpc.closed.CompareAndSwap(false, true) {
277 _ = fcpc.SetReadDeadline(time.Now()) // unblock ReadFrom() calls to kick old servers out of their loops
278 _, _ = listenerPool.Delete(fcpc.sharedPacketConn.key)
279 }
280 return nil
281 }
282
283 func (fcpc *fakeClosePacketConn) Unwrap() net.PacketConn {
284 return fcpc.sharedPacketConn.PacketConn
285 }
286
287 // sharedPacketConn is like sharedListener, but for net.PacketConns.
288 type sharedPacketConn struct {
289 net.PacketConn
290 key string
291 }
292
293 // Destruct closes the underlying socket.
294 func (spc *sharedPacketConn) Destruct() error {
295 return spc.PacketConn.Close()
296 }
297
298 // Unwrap returns the underlying socket
299 func (spc *sharedPacketConn) Unwrap() net.PacketConn {
300 return spc.PacketConn
301 }
302
303 // Interface guards (see https://github.com/caddyserver/caddy/issues/3998)
304 var (
305 _ (interface {
306 Unwrap() net.PacketConn
307 }) = (*fakeClosePacketConn)(nil)
308 )
309
310 // socketFiles is a fd -> *os.File map used to make a FileListener/FilePacketConn from a socket file descriptor.
311 var socketFiles = map[uintptr]*os.File{}
312
313 // socketFilesMu synchronizes socketFiles insertions
314 var socketFilesMu sync.Mutex