diff --git a/listeners.go b/listeners.go index 15ffbbeef92..1ae06c3d173 100644 --- a/listeners.go +++ b/listeners.go @@ -17,6 +17,7 @@ package caddy import ( "fmt" "net" + "os" "strconv" "strings" "sync" @@ -39,6 +40,9 @@ func Listen(network, addr string) (net.Listener, error) { sharedLn, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) { ln, err := net.Listen(network, addr) if err != nil { + if isUnixNetwork(network) && isListenBindAddressAlreadyInUseError(err) { + return nil, explainUnixBindAddressAlreadyInUseError(err) + } return nil, err } return &sharedListener{Listener: ln, key: lnKey}, nil @@ -59,6 +63,9 @@ func ListenPacket(network, addr string) (net.PacketConn, error) { sharedPc, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) { pc, err := net.ListenPacket(network, addr) if err != nil { + if isUnixNetwork(network) && isListenBindAddressAlreadyInUseError(err) { + return nil, explainUnixBindAddressAlreadyInUseError(err) + } return nil, err } return &sharedPacketConn{PacketConn: pc, key: lnKey}, nil @@ -322,6 +329,44 @@ func isUnixNetwork(netw string) bool { return netw == "unix" || netw == "unixgram" || netw == "unixpacket" } +func isListenBindAddressAlreadyInUseError(err error) bool { + switch networkOperationError := err.(type) { + case *net.OpError: + switch syscallError := networkOperationError.Err.(type) { + case *os.SyscallError: + if syscallError.Syscall == "bind" { + return true + } + } + } + + return false +} + +type ErrorWithExplanation struct { + Explanation string + Err error +} + +func (e *ErrorWithExplanation) Unwrap() error { return e.Err } +func (e *ErrorWithExplanation) Error() string { + if e == nil { + return "\n" + e.Explanation + } + return e.Err.Error() + "\n" + e.Explanation +} + +func explainUnixBindAddressAlreadyInUseError(err error) error { + return &ErrorWithExplanation{ + Explanation: "This can happen either when you have two programs " + + "(or two instances of caddy) attempting to listen on the same " + + "socket file at the same time, or when caddy did not exit cleanly " + + "last time it was run and thus did not get a chance to clean up " + + "the socket file on shutdown", + Err: err, + } +} + // 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