Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 13, 11:45 UTC
core: Reuse unix sockets (UDS) and don't try to serve HTTP/3 over UDS (#5063)
* core: Reuse unix sockets * Don't serve HTTP/3 over unix sockets This requires upstream support, if even useful * Don't use unix build tag... yet * Fix build tag * Allow ErrNotExist when unlinking socket
Matt Holt committed
Sep 21, 2022 at 12:55 UTC
1426c97da57fc84d8b584a960c43fbb58df68b80
4 files changed
+134
-36
listen.go
+18
-1
@@ -1,4 +1,21 @@
1
-//go:build !linux
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
+// TODO: Go 1.19 introduced the "unix" build tag. We have to support Go 1.18 until Go 1.20 is released.
16
+// When Go 1.19 is our minimum, change this build tag to simply "!unix".
17
+// (see similar change needed in listen_unix.go)
18
+//go:build !(aix || android || darwin || dragonfly || freebsd || hurd || illumos || ios || linux || netbsd || openbsd || solaris)
19
20
package caddy
21
listen_linux.go
deleted
-34
@@ -1,34 +0,0 @@
1
-package caddy
2
-
3
-import (
4
- "context"
5
- "net"
6
- "syscall"
7
- "time"
8
-
9
- "go.uber.org/zap"
10
- "golang.org/x/sys/unix"
11
-)
12
-
13
-// ListenTimeout is the same as Listen, but with a configurable keep-alive timeout duration.
14
-func ListenTimeout(network, addr string, keepalivePeriod time.Duration) (net.Listener, error) {
15
- // check to see if plugin provides listener
16
- if ln, err := getListenerFromPlugin(network, addr); err != nil || ln != nil {
17
- return ln, err
18
- }
19
-
20
- config := &net.ListenConfig{Control: reusePort, KeepAlive: keepalivePeriod}
21
- return config.Listen(context.Background(), network, addr)
22
-}
23
-
24
-func reusePort(network, address string, conn syscall.RawConn) error {
25
- return conn.Control(func(descriptor uintptr) {
26
- if err := unix.SetsockoptInt(int(descriptor), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1); err != nil {
27
- Log().Error("setting SO_REUSEPORT",
28
- zap.String("network", network),
29
- zap.String("address", address),
30
- zap.Uintptr("descriptor", descriptor),
31
- zap.Error(err))
32
- }
33
- })
34
-}
listen_unix.go
new
+115
@@ -0,0 +1,115 @@
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
+// TODO: Go 1.19 introduced the "unix" build tag. We have to support Go 1.18 until Go 1.20 is released.
16
+// When Go 1.19 is our minimum, remove this build tag, since "_unix" in the filename will do this.
17
+// (see also change needed in listen.go)
18
+//go:build aix || android || darwin || dragonfly || freebsd || hurd || illumos || ios || linux || netbsd || openbsd || solaris
19
+
20
+package caddy
21
+
22
+import (
23
+ "context"
24
+ "errors"
25
+ "io/fs"
26
+ "net"
27
+ "sync"
28
+ "syscall"
29
+ "time"
30
+
31
+ "go.uber.org/zap"
32
+ "golang.org/x/sys/unix"
33
+)
34
+
35
+// ListenTimeout is the same as Listen, but with a configurable keep-alive timeout duration.
36
+func ListenTimeout(network, addr string, keepalivePeriod time.Duration) (net.Listener, error) {
37
+ // check to see if plugin provides listener
38
+ if ln, err := getListenerFromPlugin(network, addr); err != nil || ln != nil {
39
+ return ln, err
40
+ }
41
+
42
+ socketKey := listenerKey(network, addr)
43
+ if isUnixNetwork(network) {
44
+ unixSocketsMu.Lock()
45
+ defer unixSocketsMu.Unlock()
46
+
47
+ socket, exists := unixSockets[socketKey]
48
+ if exists {
49
+ // make copy of file descriptor
50
+ socketFile, err := socket.File() // dup() deep down
51
+ if err != nil {
52
+ return nil, err
53
+ }
54
+
55
+ // use copy to make new listener
56
+ ln, err := net.FileListener(socketFile)
57
+ if err != nil {
58
+ return nil, err
59
+ }
60
+
61
+ // the old socket fd will likely be closed soon, so replace it in the map
62
+ unixSockets[socketKey] = ln.(*net.UnixListener)
63
+
64
+ return ln.(*net.UnixListener), nil
65
+ }
66
+
67
+ // from what I can tell after some quick research, it's quite common for programs to
68
+ // leave their socket file behind after they close, so the typical pattern is to
69
+ // unlink it before you bind to it -- this is often crucial if the last program using
70
+ // it was killed forcefully without a chance to clean up the socket, but there is a
71
+ // race, as the comment in net.UnixListener.close() explains... oh well?
72
+ if err := syscall.Unlink(addr); err != nil && !errors.Is(err, fs.ErrNotExist) {
73
+ return nil, err
74
+ }
75
+ }
76
+
77
+ config := &net.ListenConfig{Control: reusePort, KeepAlive: keepalivePeriod}
78
+
79
+ ln, err := config.Listen(context.Background(), network, addr)
80
+ if err != nil {
81
+ return nil, err
82
+ }
83
+
84
+ if uln, ok := ln.(*net.UnixListener); ok {
85
+ // TODO: ideally, we should unlink the socket once we know we're done using it
86
+ // (i.e. either on exit or a new config that doesn't use this socket; in UsagePool
87
+ // terms, when the reference count reaches 0), but given that we unlink existing
88
+ // socket before we create the new one anyway (see above), we don't necessarily
89
+ // need to clean up after ourselves; still, doing so would probably be more tidy
90
+ uln.SetUnlinkOnClose(false)
91
+ unixSockets[socketKey] = uln
92
+ }
93
+
94
+ return ln, nil
95
+}
96
+
97
+// reusePort sets SO_REUSEPORT. Ineffective for unix sockets.
98
+func reusePort(network, address string, conn syscall.RawConn) error {
99
+ return conn.Control(func(descriptor uintptr) {
100
+ if err := unix.SetsockoptInt(int(descriptor), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1); err != nil {
101
+ Log().Error("setting SO_REUSEPORT",
102
+ zap.String("network", network),
103
+ zap.String("address", address),
104
+ zap.Uintptr("descriptor", descriptor),
105
+ zap.Error(err))
106
+ }
107
+ })
108
+}
109
+
110
+// unixSockets keeps track of the currently-active unix sockets
111
+// so we can transfer their FDs gracefully during reloads.
112
+var (
113
+ unixSockets = make(map[string]*net.UnixListener)
114
+ unixSocketsMu sync.Mutex
115
+)
modules/caddyhttp/app.go
+1
-1
@@ -409,7 +409,7 @@ func (app *App) Start() error {
409
ln = tls.NewListener(ln, tlsCfg)
410
411
// enable HTTP/3 if configured
412
- if srv.protocol("h3") {
412
+ if srv.protocol("h3") && !listenAddr.IsUnixNetwork() {
413
app.logger.Info("enabling HTTP/3 listener", zap.String("addr", hostport))
414
if err := srv.serveHTTP3(hostport, tlsCfg); err != nil {
415
return err