From c05e3898b924c3434ee17e9e50cde2bc498e9f88 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Thu, 9 Mar 2023 07:58:31 -0800 Subject: caddyhttp: Enable 0-RTT QUIC (#5425) --- listeners.go | 1 + 1 file changed, 1 insertion(+) (limited to 'listeners.go') diff --git a/listeners.go b/listeners.go index f922144..7c5401a 100644 --- a/listeners.go +++ b/listeners.go @@ -441,6 +441,7 @@ func ListenQUIC(ln net.PacketConn, tlsConf *tls.Config, activeRequests *int64) ( sharedEarlyListener, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) { earlyLn, err := quic.ListenEarly(ln, http3.ConfigureTLSConfig(tlsConf), &quic.Config{ + Allow0RTT: func(net.Addr) bool { return true }, RequireAddressValidation: func(clientAddr net.Addr) bool { var highLoad bool if activeRequests != nil { -- cgit v1.2.3 From 3f20a7c9f348122d5fae7074b9fa17651189bb9a Mon Sep 17 00:00:00 2001 From: Francis Lavoie Date: Wed, 3 May 2023 13:07:22 -0400 Subject: acmeserver: Configurable `resolvers`, fix smallstep deprecations (#5500) * acmeserver: Configurable `resolvers`, fix smallstep deprecations * Improve default net/port * Update proxy resolvers parsing to use the new function * Update listeners.go Co-authored-by: itsxaos <33079230+itsxaos@users.noreply.github.com> --------- Co-authored-by: itsxaos <33079230+itsxaos@users.noreply.github.com> --- listeners.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'listeners.go') diff --git a/listeners.go b/listeners.go index 7c5401a..14a5c49 100644 --- a/listeners.go +++ b/listeners.go @@ -303,13 +303,19 @@ func IsUnixNetwork(netw string) bool { // Network addresses are distinct from URLs and do not // use URL syntax. func ParseNetworkAddress(addr string) (NetworkAddress, error) { + return ParseNetworkAddressWithDefaults(addr, "tcp", 0) +} + +// ParseNetworkAddressWithDefaults is like ParseNetworkAddress but allows +// the default network and port to be specified. +func ParseNetworkAddressWithDefaults(addr, defaultNetwork string, defaultPort uint) (NetworkAddress, error) { var host, port string network, host, port, err := SplitNetworkAddress(addr) if err != nil { return NetworkAddress{}, err } if network == "" { - network = "tcp" + network = defaultNetwork } if IsUnixNetwork(network) { return NetworkAddress{ @@ -318,7 +324,10 @@ func ParseNetworkAddress(addr string) (NetworkAddress, error) { }, nil } var start, end uint64 - if port != "" { + if port == "" { + start = uint64(defaultPort) + end = uint64(defaultPort) + } else { before, after, found := strings.Cut(port, "-") if !found { after = before -- cgit v1.2.3 From 808b05c3b47dc82d7e13fccee00a67c45a072a23 Mon Sep 17 00:00:00 2001 From: WeidiDeng Date: Thu, 11 May 2023 04:25:09 +0800 Subject: caddyhttp: Update quic's TLS configs after reload (#5517) (fix #4849) * fix http3 outdated certificates after config reload * delegate quic tls GetConfigForClient to another struct. * change type and method names fix lint --------- Co-authored-by: Matt Holt --- listeners.go | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 79 insertions(+), 5 deletions(-) (limited to 'listeners.go') diff --git a/listeners.go b/listeners.go index 14a5c49..4c851bd 100644 --- a/listeners.go +++ b/listeners.go @@ -449,7 +449,11 @@ func ListenQUIC(ln net.PacketConn, tlsConf *tls.Config, activeRequests *int64) ( lnKey := listenerKey("quic+"+ln.LocalAddr().Network(), ln.LocalAddr().String()) sharedEarlyListener, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) { - earlyLn, err := quic.ListenEarly(ln, http3.ConfigureTLSConfig(tlsConf), &quic.Config{ + sqtc := newSharedQUICTLSConfig(tlsConf) + // http3.ConfigureTLSConfig only uses this field and tls App sets this field as well + //nolint:gosec + quicTlsConfig := &tls.Config{GetConfigForClient: sqtc.getConfigForClient} + earlyLn, err := quic.ListenEarly(ln, http3.ConfigureTLSConfig(quicTlsConfig), &quic.Config{ Allow0RTT: func(net.Addr) bool { return true }, RequireAddressValidation: func(clientAddr net.Addr) bool { var highLoad bool @@ -462,12 +466,16 @@ func ListenQUIC(ln net.PacketConn, tlsConf *tls.Config, activeRequests *int64) ( if err != nil { return nil, err } - return &sharedQuicListener{EarlyListener: earlyLn, key: lnKey}, nil + return &sharedQuicListener{EarlyListener: earlyLn, sqtc: sqtc, key: lnKey}, nil }) if err != nil { return nil, err } + sql := sharedEarlyListener.(*sharedQuicListener) + // add current tls.Config to sqtc, so GetConfigForClient will always return the latest tls.Config in case of context cancellation + ctx, cancel := sql.sqtc.addTLSConfig(tlsConf) + // TODO: to serve QUIC over a unix socket, currently we need to hold onto // the underlying net.PacketConn (which we wrap as unixConn to keep count // of closes) because closing the quic.EarlyListener doesn't actually close @@ -479,9 +487,8 @@ func ListenQUIC(ln net.PacketConn, tlsConf *tls.Config, activeRequests *int64) ( unix = uc } - ctx, cancel := context.WithCancel(context.Background()) return &fakeCloseQuicListener{ - sharedQuicListener: sharedEarlyListener.(*sharedQuicListener), + sharedQuicListener: sql, uc: unix, context: ctx, contextCancel: cancel, @@ -494,10 +501,77 @@ func ListenerUsage(network, addr string) int { return count } +// contextAndCancelFunc groups context and its cancelFunc +type contextAndCancelFunc struct { + context.Context + context.CancelFunc +} + +// sharedQUICTLSConfig manages GetConfigForClient +// see issue: https://github.com/caddyserver/caddy/pull/4849 +type sharedQUICTLSConfig struct { + rmu sync.RWMutex + tlsConfs map[*tls.Config]contextAndCancelFunc + activeTlsConf *tls.Config +} + +// newSharedQUICTLSConfig creates a new sharedQUICTLSConfig +func newSharedQUICTLSConfig(tlsConfig *tls.Config) *sharedQUICTLSConfig { + sqtc := &sharedQUICTLSConfig{ + tlsConfs: make(map[*tls.Config]contextAndCancelFunc), + activeTlsConf: tlsConfig, + } + sqtc.addTLSConfig(tlsConfig) + return sqtc +} + +// getConfigForClient is used as tls.Config's GetConfigForClient field +func (sqtc *sharedQUICTLSConfig) getConfigForClient(ch *tls.ClientHelloInfo) (*tls.Config, error) { + sqtc.rmu.RLock() + defer sqtc.rmu.RUnlock() + return sqtc.activeTlsConf.GetConfigForClient(ch) +} + +// addTLSConfig adds tls.Config to the map if not present and returns the corresponding context and its cancelFunc +// so that when cancelled, the active tls.Config will change +func (sqtc *sharedQUICTLSConfig) addTLSConfig(tlsConfig *tls.Config) (context.Context, context.CancelFunc) { + sqtc.rmu.Lock() + defer sqtc.rmu.Unlock() + + if cacc, ok := sqtc.tlsConfs[tlsConfig]; ok { + return cacc.Context, cacc.CancelFunc + } + + ctx, cancel := context.WithCancel(context.Background()) + wrappedCancel := func() { + cancel() + + sqtc.rmu.Lock() + defer sqtc.rmu.Unlock() + + delete(sqtc.tlsConfs, tlsConfig) + if sqtc.activeTlsConf == tlsConfig { + // select another tls.Config, if there is none, + // related sharedQuicListener will be destroyed anyway + for tc := range sqtc.tlsConfs { + sqtc.activeTlsConf = tc + break + } + } + } + sqtc.tlsConfs[tlsConfig] = contextAndCancelFunc{ctx, wrappedCancel} + // there should be at most 2 tls.Configs + if len(sqtc.tlsConfs) > 2 { + Log().Warn("quic listener tls configs are more than 2", zap.Int("number of configs", len(sqtc.tlsConfs))) + } + return ctx, wrappedCancel +} + // sharedQuicListener is like sharedListener, but for quic.EarlyListeners. type sharedQuicListener struct { quic.EarlyListener - key string + sqtc *sharedQUICTLSConfig + key string } // Destruct closes the underlying QUIC listener. -- cgit v1.2.3 From 467b7e3a9cba9570cc62934316dbd251860fed3f Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Tue, 30 May 2023 12:41:57 +0300 Subject: update quic-go to v0.35.0 (#5560) --- listeners.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'listeners.go') diff --git a/listeners.go b/listeners.go index 4c851bd..672bd83 100644 --- a/listeners.go +++ b/listeners.go @@ -445,7 +445,7 @@ func ListenPacket(network, addr string) (net.PacketConn, error) { // NOTE: This API is EXPERIMENTAL and may be changed or removed. // // TODO: See if we can find a more elegant solution closer to the new NetworkAddress.Listen API. -func ListenQUIC(ln net.PacketConn, tlsConf *tls.Config, activeRequests *int64) (quic.EarlyListener, error) { +func ListenQUIC(ln net.PacketConn, tlsConf *tls.Config, activeRequests *int64) (http3.QUICEarlyListener, error) { lnKey := listenerKey("quic+"+ln.LocalAddr().Network(), ln.LocalAddr().String()) sharedEarlyListener, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) { @@ -454,7 +454,7 @@ func ListenQUIC(ln net.PacketConn, tlsConf *tls.Config, activeRequests *int64) ( //nolint:gosec quicTlsConfig := &tls.Config{GetConfigForClient: sqtc.getConfigForClient} earlyLn, err := quic.ListenEarly(ln, http3.ConfigureTLSConfig(quicTlsConfig), &quic.Config{ - Allow0RTT: func(net.Addr) bool { return true }, + Allow0RTT: true, RequireAddressValidation: func(clientAddr net.Addr) bool { var highLoad bool if activeRequests != nil { @@ -569,7 +569,7 @@ func (sqtc *sharedQUICTLSConfig) addTLSConfig(tlsConfig *tls.Config) (context.Co // sharedQuicListener is like sharedListener, but for quic.EarlyListeners. type sharedQuicListener struct { - quic.EarlyListener + *quic.EarlyListener sqtc *sharedQUICTLSConfig key string } -- cgit v1.2.3 From 806341e089ed3e5ac825c4844f6fa4d437fdb642 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Wed, 21 Jun 2023 17:16:01 -0600 Subject: core: Properly preserve unix sockets (fix #5568) --- listeners.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'listeners.go') diff --git a/listeners.go b/listeners.go index 672bd83..1429b14 100644 --- a/listeners.go +++ b/listeners.go @@ -189,13 +189,15 @@ func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net // if new listener is a unix socket, make sure we can reuse it later // (we do our own "unlink on close" -- not required, but more tidy) one := int32(1) - switch unix := ln.(type) { - case *net.UnixListener: - unix.SetUnlinkOnClose(false) - ln = &unixListener{unix, lnKey, &one} - unixSockets[lnKey] = ln.(*unixListener) + switch lnValue := ln.(type) { + case deleteListener: + if unix, ok := lnValue.Listener.(*net.UnixListener); ok { + unix.SetUnlinkOnClose(false) + ln = &unixListener{unix, lnKey, &one} + unixSockets[lnKey] = ln.(*unixListener) + } case *net.UnixConn: - ln = &unixConn{unix, address, lnKey, &one} + ln = &unixConn{lnValue, address, lnKey, &one} unixSockets[lnKey] = ln.(*unixConn) } -- cgit v1.2.3 From 9563666bfb93f1708b044c7b1e5d0aa91afd029a Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Wed, 21 Jun 2023 17:47:23 -0600 Subject: Fix compile on Windows, hopefully --- listeners.go | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) (limited to 'listeners.go') diff --git a/listeners.go b/listeners.go index 1429b14..08bdbcf 100644 --- a/listeners.go +++ b/listeners.go @@ -186,19 +186,11 @@ func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net return nil, fmt.Errorf("unsupported network type: %s", na.Network) } - // if new listener is a unix socket, make sure we can reuse it later - // (we do our own "unlink on close" -- not required, but more tidy) - one := int32(1) - switch lnValue := ln.(type) { - case deleteListener: - if unix, ok := lnValue.Listener.(*net.UnixListener); ok { - unix.SetUnlinkOnClose(false) - ln = &unixListener{unix, lnKey, &one} - unixSockets[lnKey] = ln.(*unixListener) - } - case *net.UnixConn: - ln = &unixConn{lnValue, address, lnKey, &one} - unixSockets[lnKey] = ln.(*unixConn) + // TODO: Not 100% sure this is necessary, but we do this for net.UnixListener in listen_unix.go, so... + if unix, ok := ln.(*net.UnixConn); ok { + one := int32(1) + ln = &unixConn{unix, address, lnKey, &one} + unixSockets[lnKey] = unix } return ln, nil -- cgit v1.2.3 From 2b2addebb8cc5dfc7d4d1d34c82f3c46159fea12 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Wed, 21 Jun 2023 17:59:54 -0600 Subject: Appease linter --- listeners.go | 20 -------------------- 1 file changed, 20 deletions(-) (limited to 'listeners.go') diff --git a/listeners.go b/listeners.go index 08bdbcf..1387d38 100644 --- a/listeners.go +++ b/listeners.go @@ -694,26 +694,6 @@ func RegisterNetwork(network string, getListener ListenerFunc) { networkTypes[network] = getListener } -type unixListener struct { - *net.UnixListener - mapKey string - count *int32 // accessed atomically -} - -func (uln *unixListener) Close() error { - newCount := atomic.AddInt32(uln.count, -1) - if newCount == 0 { - defer func() { - addr := uln.Addr().String() - unixSocketsMu.Lock() - delete(unixSockets, uln.mapKey) - unixSocketsMu.Unlock() - _ = syscall.Unlink(addr) - }() - } - return uln.UnixListener.Close() -} - type unixConn struct { *net.UnixConn filename string -- cgit v1.2.3 From 22927e278dc29c9d1804c20f483510ec569f23ed Mon Sep 17 00:00:00 2001 From: Emily Date: Fri, 23 Jun 2023 22:49:41 +0200 Subject: core: Add optional unix socket file permissions (#4741) * core: Add optional unix socket file permissions This commit also changes the default unix socket file permissions to `u=w,g=,o=` (octal: `0200`). It used to default to the shell's umask (usually `u=rwx,g=rx,o=rx`, octal: `0755`). `/run/caddy.sock` -> `/run/caddy.sock` with `0200` default perms `/run/caddy.sock|0222` -> `/run/caddy.sock` with `0222` perms `|` instead of `:` is used as a separator, to account for the `:` in Windows drive letters (e.g. `C:\absolute\path.sock`) Fun fact: The old unix(7) man page (pre Jun 2016) stated a socket needs both read and write perms. Turns out, only write perms are needed. Corrected in https://github.com/mkerrisk/man-pages/commit/7578ea2f85b272363d22680d69e7d32f0b59c83b Despite this, most implementations still default to read+write to this date. * Add cases with Windows paths to test * Require write perms for the owning user --- listeners.go | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 3 deletions(-) (limited to 'listeners.go') diff --git a/listeners.go b/listeners.go index 1387d38..cbce68d 100644 --- a/listeners.go +++ b/listeners.go @@ -20,6 +20,7 @@ import ( "errors" "fmt" "io" + "io/fs" "net" "net/netip" "os" @@ -148,11 +149,27 @@ func (na NetworkAddress) Listen(ctx context.Context, portOffset uint, config net func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net.ListenConfig) (any, error) { var ln any var err error + var address string + var unixFileMode fs.FileMode - address := na.JoinHostPort(portOffset) + // split unix socket addr early so lnKey + // is independent of permissions bits + if na.IsUnixNetwork() { + var err error + address, unixFileMode, err = splitUnixSocketPermissionsBits(na.Host) + if err != nil { + return nil, err + } + } else { + address = na.JoinHostPort(portOffset) + } - // if this is a unix socket, see if we already have it open + // if this is a unix socket, see if we already have it open, + // force socket permissions on it and return early if socket, err := reuseUnixSocket(na.Network, address); socket != nil || err != nil { + if err := os.Chmod(address, unixFileMode); err != nil { + return nil, fmt.Errorf("unable to set permissions (%s) on %s: %v", unixFileMode, address, err) + } return socket, err } @@ -193,6 +210,12 @@ func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net unixSockets[lnKey] = unix } + if IsUnixNetwork(na.Network) { + if err := os.Chmod(address, unixFileMode); err != nil { + return nil, fmt.Errorf("unable to set permissions (%s) on %s: %v", unixFileMode, address, err) + } + } + return ln, nil } @@ -288,6 +311,40 @@ func IsUnixNetwork(netw string) bool { return strings.HasPrefix(netw, "unix") } +// Takes a unix socket address in the unusual "path|bits" format +// (e.g. /run/caddy.sock|0222) and tries to split it into +// socket path (host) and permissions bits (port). Colons (":") +// can't be used as separator, as socket paths on Windows may +// include a drive letter (e.g. `unix/c:\absolute\path.sock`). +// Permission bits will default to 0200 if none are specified. +// Throws an error, if the first carrying bit does not +// include write perms (e.g. `0422` or `022`). +// Symbolic permission representation (e.g. `u=w,g=w,o=w`) +// is not supported and will throw an error for now! +func splitUnixSocketPermissionsBits(addr string) (path string, fileMode fs.FileMode, err error) { + addrSplit := strings.SplitN(addr, "|", 2) + + if len(addrSplit) == 2 { + // parse octal permission bit string as uint32 + fileModeUInt64, err := strconv.ParseUint(addrSplit[1], 8, 32) + if err != nil { + return "", 0, fmt.Errorf("could not parse octal permission bits in %s: %v", addr, err) + } + fileMode = fs.FileMode(fileModeUInt64) + + // FileMode.String() returns a string like `-rwxr-xr--` for `u=rwx,g=rx,o=r` (`0754`) + if string(fileMode.String()[2]) != "w" { + return "", 0, fmt.Errorf("owner of the socket requires '-w-' (write, octal: '2') permissions at least; got '%s' in %s", fileMode.String()[1:4], addr) + } + + return addrSplit[0], fileMode, nil + } + + // default to 0200 (symbolic: `u=w,g=,o=`) + // if no permission bits are specified + return addr, 0200, nil +} + // ParseNetworkAddress parses addr into its individual // components. The input string is expected to be of // the form "network/host:port-range" where any part is @@ -312,10 +369,11 @@ func ParseNetworkAddressWithDefaults(addr, defaultNetwork string, defaultPort ui network = defaultNetwork } if IsUnixNetwork(network) { + _, _, err := splitUnixSocketPermissionsBits(host) return NetworkAddress{ Network: network, Host: host, - }, nil + }, err } var start, end uint64 if port == "" { -- cgit v1.2.3 From 119e8794bcbda80ca337c1bb6164718e1490cc4f Mon Sep 17 00:00:00 2001 From: Emily Date: Sun, 25 Jun 2023 02:25:02 +0200 Subject: core: Skip `chmod` for abstract unix sockets (#5596) because those aren't real paths on the filesystem and thus can't be `chmod`ed --- listeners.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'listeners.go') diff --git a/listeners.go b/listeners.go index cbce68d..a12d67f 100644 --- a/listeners.go +++ b/listeners.go @@ -151,6 +151,7 @@ func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net var err error var address string var unixFileMode fs.FileMode + var isAbtractUnixSocket bool // split unix socket addr early so lnKey // is independent of permissions bits @@ -160,6 +161,7 @@ func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net if err != nil { return nil, err } + isAbtractUnixSocket = strings.HasPrefix(address, "@") } else { address = na.JoinHostPort(portOffset) } @@ -167,8 +169,11 @@ func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net // if this is a unix socket, see if we already have it open, // force socket permissions on it and return early if socket, err := reuseUnixSocket(na.Network, address); socket != nil || err != nil { - if err := os.Chmod(address, unixFileMode); err != nil { - return nil, fmt.Errorf("unable to set permissions (%s) on %s: %v", unixFileMode, address, err) + if !isAbtractUnixSocket { + if err := os.Chmod(address, unixFileMode); err != nil { + return nil, fmt.Errorf("unable to set permissions (%s) on %s: %v", unixFileMode, address, err) + } + } return socket, err } @@ -211,8 +216,10 @@ func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net } if IsUnixNetwork(na.Network) { - if err := os.Chmod(address, unixFileMode); err != nil { - return nil, fmt.Errorf("unable to set permissions (%s) on %s: %v", unixFileMode, address, err) + if !isAbtractUnixSocket { + if err := os.Chmod(address, unixFileMode); err != nil { + return nil, fmt.Errorf("unable to set permissions (%s) on %s: %v", unixFileMode, address, err) + } } } -- cgit v1.2.3 From 710824c3ce9f8084517e8ab099d57f9060f62061 Mon Sep 17 00:00:00 2001 From: WeidiDeng Date: Sat, 1 Jul 2023 06:31:26 +0800 Subject: core: Embed net.UDPConn to gain optimizations (#5606) Co-authored-by: Matt Holt --- listeners.go | 40 +++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 23 deletions(-) (limited to 'listeners.go') diff --git a/listeners.go b/listeners.go index a12d67f..fe5ad0c 100644 --- a/listeners.go +++ b/listeners.go @@ -196,7 +196,8 @@ func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net if err != nil { return nil, err } - ln = &fakeClosePacketConn{sharedPacketConn: sharedPc.(*sharedPacketConn)} + spc := sharedPc.(*sharedPacketConn) + ln = &fakeClosePacketConn{spc: spc, UDPConn: spc.PacketConn.(*net.UDPConn)} } if strings.HasPrefix(na.Network, "ip") { ln, err = config.ListenPacket(ctx, na.Network, address) @@ -668,37 +669,30 @@ func fakeClosedErr(l interface{ Addr() net.Addr }) error { // socket is actually left open. var errFakeClosed = fmt.Errorf("listener 'closed' 😉") -// fakeClosePacketConn is like fakeCloseListener, but for PacketConns. +// fakeClosePacketConn is like fakeCloseListener, but for PacketConns, +// or more specifically, *net.UDPConn type fakeClosePacketConn struct { - closed int32 // accessed atomically; belongs to this struct only - *sharedPacketConn // embedded, so we also become a net.PacketConn + closed int32 // accessed atomically; belongs to this struct only + spc *sharedPacketConn // its key is used in Close + *net.UDPConn // embedded, so we also become a net.PacketConn and enable several other optimizations done by quic-go } +// interface guard for extra optimizations +// needed by QUIC implementation: https://github.com/caddyserver/caddy/issues/3998, https://github.com/caddyserver/caddy/issues/5605 +var _ quic.OOBCapablePacketConn = (*fakeClosePacketConn)(nil) + +// https://pkg.go.dev/golang.org/x/net/ipv4#NewPacketConn is used by quic-go and requires a net.PacketConn type assertable to a net.Conn, +// but doesn't actually use these methods, the only methods needed are `ReadMsgUDP` and `SyscallConn`. +var _ net.Conn = (*fakeClosePacketConn)(nil) + +// Close won't close the underlying socket unless there is no more reference, then listenerPool will close it. func (fcpc *fakeClosePacketConn) Close() error { if atomic.CompareAndSwapInt32(&fcpc.closed, 0, 1) { - _, _ = listenerPool.Delete(fcpc.sharedPacketConn.key) + _, _ = listenerPool.Delete(fcpc.spc.key) } return nil } -// Supports QUIC implementation: https://github.com/caddyserver/caddy/issues/3998 -func (fcpc fakeClosePacketConn) SetReadBuffer(bytes int) error { - if conn, ok := fcpc.PacketConn.(interface{ SetReadBuffer(int) error }); ok { - return conn.SetReadBuffer(bytes) - } - return fmt.Errorf("SetReadBuffer() not implemented for %T", fcpc.PacketConn) -} - -// Supports QUIC implementation: https://github.com/caddyserver/caddy/issues/3998 -func (fcpc fakeClosePacketConn) SyscallConn() (syscall.RawConn, error) { - if conn, ok := fcpc.PacketConn.(interface { - SyscallConn() (syscall.RawConn, error) - }); ok { - return conn.SyscallConn() - } - return nil, fmt.Errorf("SyscallConn() not implemented for %T", fcpc.PacketConn) -} - type fakeCloseQuicListener struct { closed int32 // accessed atomically; belongs to this struct only *sharedQuicListener // embedded, so we also become a quic.EarlyListener -- cgit v1.2.3 From 8d304a4566de36219b31e1cb5a636431362c673c Mon Sep 17 00:00:00 2001 From: Emily Date: Sun, 6 Aug 2023 02:09:16 +0200 Subject: cmd: Split unix sockets for admin endpoint addresses (#5696) * cmd: fix cli when admin endpoint uses new unix socket permission format Fixes a bug where the following Caddyfile ```Caddyfile { admin unix/admin.sock|0660 } ``` and `caddy reload --config Caddyfile` would throw the following error instead of reloading it: ``` INFO using provided configuration {"config_file": "Caddyfile", "config_adapter": ""} Error: sending configuration to instance: performing request: Post "http://127.0.0.1/load": dial unix admin.sock|0660: connect: no such file or directory [ERROR] exit status 1 ``` --- This bug also affected `caddy start` and `caddy stop`. * Move splitter function to internal --------- Co-authored-by: Matthew Holt --- listeners.go | 39 +++------------------------------------ 1 file changed, 3 insertions(+), 36 deletions(-) (limited to 'listeners.go') diff --git a/listeners.go b/listeners.go index fe5ad0c..9e761be 100644 --- a/listeners.go +++ b/listeners.go @@ -31,6 +31,7 @@ import ( "syscall" "time" + "github.com/caddyserver/caddy/v2/internal" "github.com/quic-go/quic-go" "github.com/quic-go/quic-go/http3" "go.uber.org/zap" @@ -157,7 +158,7 @@ func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net // is independent of permissions bits if na.IsUnixNetwork() { var err error - address, unixFileMode, err = splitUnixSocketPermissionsBits(na.Host) + address, unixFileMode, err = internal.SplitUnixSocketPermissionsBits(na.Host) if err != nil { return nil, err } @@ -319,40 +320,6 @@ func IsUnixNetwork(netw string) bool { return strings.HasPrefix(netw, "unix") } -// Takes a unix socket address in the unusual "path|bits" format -// (e.g. /run/caddy.sock|0222) and tries to split it into -// socket path (host) and permissions bits (port). Colons (":") -// can't be used as separator, as socket paths on Windows may -// include a drive letter (e.g. `unix/c:\absolute\path.sock`). -// Permission bits will default to 0200 if none are specified. -// Throws an error, if the first carrying bit does not -// include write perms (e.g. `0422` or `022`). -// Symbolic permission representation (e.g. `u=w,g=w,o=w`) -// is not supported and will throw an error for now! -func splitUnixSocketPermissionsBits(addr string) (path string, fileMode fs.FileMode, err error) { - addrSplit := strings.SplitN(addr, "|", 2) - - if len(addrSplit) == 2 { - // parse octal permission bit string as uint32 - fileModeUInt64, err := strconv.ParseUint(addrSplit[1], 8, 32) - if err != nil { - return "", 0, fmt.Errorf("could not parse octal permission bits in %s: %v", addr, err) - } - fileMode = fs.FileMode(fileModeUInt64) - - // FileMode.String() returns a string like `-rwxr-xr--` for `u=rwx,g=rx,o=r` (`0754`) - if string(fileMode.String()[2]) != "w" { - return "", 0, fmt.Errorf("owner of the socket requires '-w-' (write, octal: '2') permissions at least; got '%s' in %s", fileMode.String()[1:4], addr) - } - - return addrSplit[0], fileMode, nil - } - - // default to 0200 (symbolic: `u=w,g=,o=`) - // if no permission bits are specified - return addr, 0200, nil -} - // ParseNetworkAddress parses addr into its individual // components. The input string is expected to be of // the form "network/host:port-range" where any part is @@ -377,7 +344,7 @@ func ParseNetworkAddressWithDefaults(addr, defaultNetwork string, defaultPort ui network = defaultNetwork } if IsUnixNetwork(network) { - _, _, err := splitUnixSocketPermissionsBits(host) + _, _, err := internal.SplitUnixSocketPermissionsBits(host) return NetworkAddress{ Network: network, Host: host, -- cgit v1.2.3 From b32f265ecad60404c3818cc9d42e367a8e4eb7d4 Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Tue, 8 Aug 2023 03:40:31 +0800 Subject: ci: Use gofumpt to format code (#5707) --- listeners.go | 1 - 1 file changed, 1 deletion(-) (limited to 'listeners.go') diff --git a/listeners.go b/listeners.go index 9e761be..4cd00b2 100644 --- a/listeners.go +++ b/listeners.go @@ -174,7 +174,6 @@ func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net if err := os.Chmod(address, unixFileMode); err != nil { return nil, fmt.Errorf("unable to set permissions (%s) on %s: %v", unixFileMode, address, err) } - } return socket, err } -- cgit v1.2.3 From d6f86cccf5fa5b4eb30141da390cf2439746c5da Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Mon, 14 Aug 2023 23:41:15 +0800 Subject: ci: use gci linter (#5708) * use gofmput to format code * use gci to format imports * reconfigure gci * linter autofixes * rearrange imports a little * export GOOS=windows golangci-lint run ./... --fix --- listeners.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'listeners.go') diff --git a/listeners.go b/listeners.go index 4cd00b2..768d977 100644 --- a/listeners.go +++ b/listeners.go @@ -31,10 +31,11 @@ import ( "syscall" "time" - "github.com/caddyserver/caddy/v2/internal" "github.com/quic-go/quic-go" "github.com/quic-go/quic-go/http3" "go.uber.org/zap" + + "github.com/caddyserver/caddy/v2/internal" ) // NetworkAddress represents one or more network addresses. -- cgit v1.2.3