vendor: github.com/docker/go-connections v0.7.0

Changes:

- raise minimum supported Go version to go1.23
- sockets: `ConfigureTransport`: prevent idle connections leaking FDs.
- sockets: implement `WithAdditionalUsersAndGroups` for windows.
- tlsconfig: add ChaCha20-Poly1305 cipher suites to align closer with stdlib defaults.
- nat: SortPortMap: accept `map[Port][]PortBinding` as argument.
- nat: add benchmarks, optimize, and improve errors.
- proxy: check for `net.ErrClosed` instead of string-matching "use of closed network connection".

Breaking changes:

- tlsconfig: deprecate `tlsconfig.SystemCertPool` in favor of stdlib `x509.SystemCertPool`.
- sockets: remove deprecated `DialPipe`, `GetProxyEnv`, `DialerFromEnvironment`

Dependency updates:

- update github.com/Microsoft/go-winio to go v0.6.2
- update golang.org/x/sys to v0.10.0

full diff: https://github.com/docker/go-connections/compare/v0.6.0...v0.7.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn
2025-10-28 21:13:29 +01:00
parent caea5f77ad
commit c6d4830c14
13 changed files with 202 additions and 119 deletions

View File

@@ -7,7 +7,7 @@ require (
github.com/containerd/errdefs v1.0.0
github.com/containerd/errdefs/pkg v0.3.0
github.com/distribution/reference v0.6.0
github.com/docker/go-connections v0.6.0
github.com/docker/go-connections v0.7.0
github.com/docker/go-units v0.5.0
github.com/google/go-cmp v0.7.0
github.com/moby/moby/api v1.54.1

View File

@@ -12,8 +12,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c=
github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=

2
go.mod
View File

@@ -35,7 +35,7 @@ require (
github.com/deckarep/golang-set/v2 v2.8.0
github.com/distribution/reference v0.6.0
github.com/docker/distribution v2.8.3+incompatible
github.com/docker/go-connections v0.6.0
github.com/docker/go-connections v0.7.0
github.com/docker/go-events v0.0.0-20250808211157-605354379745
github.com/docker/go-metrics v0.0.1
github.com/docker/go-units v0.5.0

4
go.sum
View File

@@ -233,8 +233,8 @@ github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBi
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY=
github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c=
github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q=
github.com/docker/go-events v0.0.0-20250808211157-605354379745 h1:yOn6Ze6IbYI/KAw2lw/83ELYvZh6hvsygTVkD0dzMC4=
github.com/docker/go-events v0.0.0-20250808211157-605354379745/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=

View File

@@ -1,48 +1,57 @@
package sockets
import (
"errors"
"net"
"sync"
)
var errClosed = errors.New("use of closed network connection")
// InmemSocket implements net.Listener using in-memory only connections.
type InmemSocket struct {
chConn chan net.Conn
chClose chan struct{}
addr string
mu sync.Mutex
}
// dummyAddr is used to satisfy net.Addr for the in-mem socket
// it is just stored as a string and returns the string for all calls
type dummyAddr string
// NewInmemSocket creates an in-memory only net.Listener
// The addr argument can be any string, but is used to satisfy the `Addr()` part
// of the net.Listener interface
// Network returns the addr string, satisfies net.Addr
func (a dummyAddr) Network() string {
return string(a)
}
// String returns the string form
func (a dummyAddr) String() string {
return string(a)
}
// InmemSocket implements [net.Listener] using in-memory only connections.
type InmemSocket struct {
chConn chan net.Conn
chClose chan struct{}
addr dummyAddr
mu sync.Mutex
}
// NewInmemSocket creates an in-memory only [net.Listener]. The addr argument
// can be any string, but is used to satisfy the [net.Listener.Addr] part
// of the [net.Listener] interface
func NewInmemSocket(addr string, bufSize int) *InmemSocket {
return &InmemSocket{
chConn: make(chan net.Conn, bufSize),
chClose: make(chan struct{}),
addr: addr,
addr: dummyAddr(addr),
}
}
// Addr returns the socket's addr string to satisfy net.Listener
func (s *InmemSocket) Addr() net.Addr {
return dummyAddr(s.addr)
return s.addr
}
// Accept implements the Accept method in the Listener interface; it waits for the next call and returns a generic Conn.
// Accept implements the Accept method in the Listener interface; it waits
// for the next call and returns a generic Conn. It returns a [net.ErrClosed]
// if the connection is already closed.
func (s *InmemSocket) Accept() (net.Conn, error) {
select {
case conn := <-s.chConn:
return conn, nil
case <-s.chClose:
return nil, errClosed
return nil, net.ErrClosed
}
}
@@ -58,24 +67,15 @@ func (s *InmemSocket) Close() error {
return nil
}
// Dial is used to establish a connection with the in-mem server
// Dial is used to establish a connection with the in-mem server.
// It returns a [net.ErrClosed] if the connection is already closed.
func (s *InmemSocket) Dial(network, addr string) (net.Conn, error) {
srvConn, clientConn := net.Pipe()
select {
case s.chConn <- srvConn:
case <-s.chClose:
return nil, errClosed
return nil, net.ErrClosed
}
return clientConn, nil
}
// Network returns the addr string, satisfies net.Addr
func (a dummyAddr) Network() string {
return string(a)
}
// String returns the string form
func (a dummyAddr) String() string {
return string(a)
}

View File

@@ -1,31 +0,0 @@
package sockets
import (
"net"
"os"
"strings"
)
// GetProxyEnv allows access to the uppercase and the lowercase forms of
// proxy-related variables. See the Go specification for details on these
// variables. https://golang.org/pkg/net/http/
//
// Deprecated: this function was used as helper for [DialerFromEnvironment] and is no longer used. It will be removed in the next release.
func GetProxyEnv(key string) string {
proxyValue := os.Getenv(strings.ToUpper(key))
if proxyValue == "" {
return os.Getenv(strings.ToLower(key))
}
return proxyValue
}
// DialerFromEnvironment was previously used to configure a net.Dialer to route
// connections through a SOCKS proxy.
//
// Deprecated: SOCKS proxies are now supported by configuring only
// http.Transport.Proxy, and no longer require changing http.Transport.Dial.
// Therefore, only [sockets.ConfigureTransport] needs to be called, and any
// [sockets.DialerFromEnvironment] calls can be dropped.
func DialerFromEnvironment(direct *net.Dialer) (*net.Dialer, error) {
return direct, nil
}

View File

@@ -27,11 +27,19 @@ var ErrProtocolNotAvailable = errors.New("protocol not available")
// make sure you do it _after_ any subsequent calls to ConfigureTransport is made against the same
// [http.Transport].
func ConfigureTransport(tr *http.Transport, proto, addr string) error {
if tr.MaxIdleConns == 0 {
// prevent long-lived processes from leaking connections
// due to idle connections not being released.
//
// TODO: see if we can also address this from the server side; see: https://github.com/moby/moby/issues/45539
tr.MaxIdleConns = 6
tr.IdleConnTimeout = 30 * time.Second
}
switch proto {
case "unix":
return configureUnixTransport(tr, proto, addr)
return configureUnixTransport(tr, addr)
case "npipe":
return configureNpipeTransport(tr, proto, addr)
return configureNpipeTransport(tr, addr)
default:
tr.Proxy = http.ProxyFromEnvironment
tr.DisableCompression = false
@@ -42,15 +50,7 @@ func ConfigureTransport(tr *http.Transport, proto, addr string) error {
return nil
}
// DialPipe connects to a Windows named pipe. It is not supported on
// non-Windows platforms.
//
// Deprecated: use [github.com/Microsoft/go-winio.DialPipe] or [github.com/Microsoft/go-winio.DialPipeContext].
func DialPipe(addr string, timeout time.Duration) (net.Conn, error) {
return dialPipe(addr, timeout)
}
func configureUnixTransport(tr *http.Transport, proto, addr string) error {
func configureUnixTransport(tr *http.Transport, addr string) error {
if len(addr) > maxUnixSocketPathSize {
return fmt.Errorf("unix socket path %q is too long", addr)
}
@@ -60,7 +60,7 @@ func configureUnixTransport(tr *http.Transport, proto, addr string) error {
Timeout: defaultTimeout,
}
tr.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) {
return dialer.DialContext(ctx, proto, addr)
return dialer.DialContext(ctx, "unix", addr)
}
return nil
}

View File

@@ -2,17 +2,6 @@
package sockets
import (
"net"
"net/http"
"syscall"
"time"
)
func configureNpipeTransport(tr *http.Transport, proto, addr string) error {
func configureNpipeTransport(any, string) error {
return ErrProtocolNotAvailable
}
func dialPipe(_ string, _ time.Duration) (net.Conn, error) {
return nil, syscall.EAFNOSUPPORT
}

View File

@@ -4,12 +4,11 @@ import (
"context"
"net"
"net/http"
"time"
"github.com/Microsoft/go-winio"
)
func configureNpipeTransport(tr *http.Transport, proto, addr string) error {
func configureNpipeTransport(tr *http.Transport, addr string) error {
// No need for compression in local communications.
tr.DisableCompression = true
tr.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) {
@@ -17,7 +16,3 @@ func configureNpipeTransport(tr *http.Transport, proto, addr string) error {
}
return nil
}
func dialPipe(addr string, timeout time.Duration) (net.Conn, error) {
return winio.DialPipe(addr, &timeout)
}

View File

@@ -1,6 +1,128 @@
package sockets
import "net"
import (
"errors"
"fmt"
"net"
"strings"
"github.com/Microsoft/go-winio"
"golang.org/x/sys/windows"
)
// BasePermissions defines the default DACL, which allows Administrators
// and LocalSystem full access (similar to defaults used in [moby]);
//
// - D:P: DACL without inheritance (protected, (P)).
// - (A;;GA;;;BA): Allow full access (GA) for built-in Administrators (BA).
// - (A;;GA;;;SY); Allow full access (GA) for LocalSystem (SY).
// - Any other user is denied access.
//
// [moby]: https://github.com/moby/moby/blob/6b45c76a233b1b8b56465f76c21c09fd7920e82d/daemon/listeners/listeners_windows.go#L53-L59
const BasePermissions = "D:P(A;;GA;;;BA)(A;;GA;;;SY)"
// WithBasePermissions sets a default DACL, which allows Administrators
// and LocalSystem full access (similar to defaults used in [moby]);
//
// - D:P: DACL without inheritance (protected, (P)).
// - (A;;GA;;;BA): Allow full access (GA) for built-in Administrators (BA).
// - (A;;GA;;;SY); Allow full access (GA) for LocalSystem (SY).
// - Any other user is denied access.
//
// [moby]: https://github.com/moby/moby/blob/6b45c76a233b1b8b56465f76c21c09fd7920e82d/daemon/listeners/listeners_windows.go#L53-L59
func WithBasePermissions() SockOption {
return withSDDL(BasePermissions)
}
// WithAdditionalUsersAndGroups modifies the socket file's DACL to grant
// access to additional users and groups.
//
// It sets [BasePermissions] on the socket path and grants the given additional
// users and groups to generic read (GR) and write (GW) access. It returns
// an error if no groups were given, when failing to resolve any of the
// additional users and groups, or when failing to apply the ACL.
func WithAdditionalUsersAndGroups(additionalUsersAndGroups []string) SockOption {
return func(path string) error {
if len(additionalUsersAndGroups) == 0 {
return errors.New("no additional users specified")
}
sd, err := getSecurityDescriptor(additionalUsersAndGroups...)
if err != nil {
return fmt.Errorf("looking up SID: %w", err)
}
return withSDDL(sd)(path)
}
}
// withSDDL applies the given SDDL to the socket. It returns an error
// when failing parse the SDDL, or if the DACL was defaulted.
//
// TODO(thaJeztah); this is not exported yet, as some of the checks may need review if they're not too opinionated.
func withSDDL(sddl string) SockOption {
return func(path string) error {
sd, err := windows.SecurityDescriptorFromString(sddl)
if err != nil {
return fmt.Errorf("parsing SDDL: %w", err)
}
dacl, defaulted, err := sd.DACL()
if err != nil {
return fmt.Errorf("extracting DACL: %w", err)
}
if dacl == nil || defaulted {
// should never be hit with our [DefaultPermissions],
// as it contains "D:" and "P" (protected, don't inherit).
return errors.New("no DACL found in security descriptor or defaulted")
}
return windows.SetNamedSecurityInfo(
path,
windows.SE_FILE_OBJECT,
windows.DACL_SECURITY_INFORMATION|windows.PROTECTED_DACL_SECURITY_INFORMATION,
nil, // do not change the owner
nil, // do not change the owner
dacl,
nil,
)
}
}
// NewUnixSocket creates a new unix socket.
//
// It sets [BasePermissions] on the socket path and grants the given additional
// users and groups to generic read (GR) and write (GW) access. It returns
// an error when failing to resolve any of the additional users and groups,
// or when failing to apply the ACL.
func NewUnixSocket(path string, additionalUsersAndGroups []string) (net.Listener, error) {
var opts []SockOption
if len(additionalUsersAndGroups) > 0 {
opts = append(opts, WithAdditionalUsersAndGroups(additionalUsersAndGroups))
} else {
opts = append(opts, WithBasePermissions())
}
return NewUnixSocketWithOpts(path, opts...)
}
// getSecurityDescriptor returns the DACL for the Unix socket.
//
// By default, it grants [BasePermissions], but allows for additional
// users and groups to get generic read (GR) and write (GW) access. It
// returns an error when failing to resolve any of the additional users
// and groups.
func getSecurityDescriptor(additionalUsersAndGroups ...string) (string, error) {
sddl := BasePermissions
// Grant generic read (GR) and write (GW) access to whatever
// additional users or groups were specified.
//
// TODO(thaJeztah): should we fail on, or remove duplicates?
for _, g := range additionalUsersAndGroups {
sid, err := winio.LookupSidByName(strings.TrimSpace(g))
if err != nil {
return "", fmt.Errorf("looking up SID: %w", err)
}
sddl += fmt.Sprintf("(A;;GRGW;;;%s)", sid)
}
return sddl, nil
}
func listenUnix(path string) (net.Listener, error) {
return net.Listen("unix", path)

View File

@@ -1,16 +1,12 @@
package tlsconfig
import (
"crypto/x509"
"runtime"
)
import "crypto/x509"
// SystemCertPool returns a copy of the system cert pool,
// returns an error if failed to load or empty pool on windows.
// SystemCertPool returns a copy of the system cert pool.
//
// Deprecated: use [x509.SystemCertPool] instead.
//
//go:fix inline
func SystemCertPool() (*x509.CertPool, error) {
certpool, err := x509.SystemCertPool()
if err != nil && runtime.GOOS == "windows" {
return x509.NewCertPool(), nil
}
return certpool, err
return x509.SystemCertPool()
}

View File

@@ -34,6 +34,9 @@ type Options struct {
// the system pool will be used.
ExclusiveRootPools bool
MinVersion uint16
// systemCertPool allows mocking the system cert-pool for testing.
systemCertPool func() (*x509.CertPool, error)
}
// DefaultServerAcceptedCiphers should be uses by code which already has a crypto/tls
@@ -47,6 +50,8 @@ var defaultCipherSuites = []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
}
// ServerDefault returns a secure-enough TLS configuration for the server TLS configuration.
@@ -75,26 +80,33 @@ func defaultConfig(ops ...func(*tls.Config)) *tls.Config {
}
// certPool returns an X.509 certificate pool from `caFile`, the certificate file.
func certPool(caFile string, exclusivePool bool) (*x509.CertPool, error) {
func certPool(opts Options) (*x509.CertPool, error) {
// If we should verify the server, we need to load a trusted ca
var (
pool *x509.CertPool
err error
)
if exclusivePool {
if opts.ExclusiveRootPools {
pool = x509.NewCertPool()
} else {
pool, err = SystemCertPool()
if opts.systemCertPool != nil {
pool, err = opts.systemCertPool()
} else {
pool, err = x509.SystemCertPool()
}
if err != nil {
return nil, fmt.Errorf("failed to read system certificates: %v", err)
}
}
pemData, err := os.ReadFile(caFile)
if opts.CAFile == "" {
return pool, nil
}
pemData, err := os.ReadFile(opts.CAFile)
if err != nil {
return nil, fmt.Errorf("could not read CA certificate %q: %v", caFile, err)
return nil, fmt.Errorf("could not read CA certificate %q: %v", opts.CAFile, err)
}
if !pool.AppendCertsFromPEM(pemData) {
return nil, fmt.Errorf("failed to append certificates from PEM file: %q", caFile)
return nil, fmt.Errorf("failed to append certificates from PEM file: %q", opts.CAFile)
}
return pool, nil
}
@@ -197,7 +209,7 @@ func Client(options Options) (*tls.Config, error) {
tlsConfig := defaultConfig()
tlsConfig.InsecureSkipVerify = options.InsecureSkipVerify
if !options.InsecureSkipVerify && options.CAFile != "" {
CAs, err := certPool(options.CAFile, options.ExclusiveRootPools)
CAs, err := certPool(options)
if err != nil {
return nil, err
}
@@ -230,7 +242,7 @@ func Server(options Options) (*tls.Config, error) {
}
tlsConfig.Certificates = []tls.Certificate{tlsCert}
if options.ClientAuth >= tls.VerifyClientCertIfGiven && options.CAFile != "" {
CAs, err := certPool(options.CAFile, options.ExclusiveRootPools)
CAs, err := certPool(options)
if err != nil {
return nil, err
}

4
vendor/modules.txt vendored
View File

@@ -620,8 +620,8 @@ github.com/docker/distribution/registry/client/auth/challenge
github.com/docker/distribution/registry/client/transport
github.com/docker/distribution/registry/storage/cache
github.com/docker/distribution/registry/storage/cache/memory
# github.com/docker/go-connections v0.6.0
## explicit; go 1.18
# github.com/docker/go-connections v0.7.0
## explicit; go 1.23
github.com/docker/go-connections/sockets
github.com/docker/go-connections/tlsconfig
# github.com/docker/go-events v0.0.0-20250808211157-605354379745