diff --git a/pkg/tcp/proxy.go b/pkg/tcp/proxy.go index a9c5f1844..8887bb91d 100644 --- a/pkg/tcp/proxy.go +++ b/pkg/tcp/proxy.go @@ -1,9 +1,11 @@ package tcp import ( + "errors" "fmt" "io" "net" + "syscall" "time" "github.com/pires/go-proxyproto" @@ -74,7 +76,14 @@ func (p *Proxy) ServeTCP(conn WriteCloser) { err = <-errChan if err != nil { - log.WithoutContext().Errorf("Error during connection: %v", err) + // Treat connection reset error during a read operation with a lower log level. + // This allows to not report an RST packet sent by the peer as an error, + // as it is an abrupt but possible end for the TCP session + if isReadConnResetError(err) { + log.WithoutContext().Debugf("Error during connection: %v", err) + } else { + log.WithoutContext().Errorf("Error during connection: %v", err) + } } <-errChan @@ -101,9 +110,18 @@ func (p Proxy) connCopy(dst, src WriteCloser, errCh chan error) { _, err := io.Copy(dst, src) errCh <- err + // Ends the connection with the dst connection peer. + // It corresponds to sending a FIN packet to gracefully end the TCP session. errClose := dst.CloseWrite() if errClose != nil { - log.WithoutContext().Debugf("Error while terminating connection: %v", errClose) + // Calling CloseWrite() on a connection which have a socket which is "not connected" is expected to fail. + // It happens notably when the dst connection has ended receiving an RST packet from the peer (within the other connCopy call). + // In that case, logging the error is superfluous, + // as in the first place we should not have needed to call CloseWrite. + if !isSocketNotConnectedError(errClose) { + log.WithoutContext().Debugf("Error while terminating connection: %v", errClose) + } + return } @@ -114,3 +132,9 @@ func (p Proxy) connCopy(dst, src WriteCloser, errCh chan error) { } } } + +// isSocketNotConnectedError reports whether err is a socket not connected error. +func isSocketNotConnectedError(err error) bool { + var oerr *net.OpError + return errors.As(err, &oerr) && errors.Is(err, syscall.ENOTCONN) +} diff --git a/pkg/tcp/proxy_unix.go b/pkg/tcp/proxy_unix.go new file mode 100644 index 000000000..727074bcc --- /dev/null +++ b/pkg/tcp/proxy_unix.go @@ -0,0 +1,16 @@ +//go:build !windows +// +build !windows + +package tcp + +import ( + "errors" + "net" + "syscall" +) + +// isReadConnResetError reports whether err is a connection reset error during a read operation. +func isReadConnResetError(err error) bool { + var oerr *net.OpError + return errors.As(err, &oerr) && oerr.Op == "read" && errors.Is(err, syscall.ECONNRESET) +} diff --git a/pkg/tcp/proxy_windows.go b/pkg/tcp/proxy_windows.go new file mode 100644 index 000000000..579d52063 --- /dev/null +++ b/pkg/tcp/proxy_windows.go @@ -0,0 +1,16 @@ +//go:build windows +// +build windows + +package tcp + +import ( + "errors" + "net" + "syscall" +) + +// isReadConnResetError reports whether err is a connection reset error during a read operation. +func isReadConnResetError(err error) bool { + var oerr *net.OpError + return errors.As(err, &oerr) && oerr.Op == "read" && errors.Is(err, syscall.WSAECONNRESET) +}