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
| 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 |