2019-03-14 11:30:04 +03:00
package tcp
import (
2022-09-14 12:50:08 +03:00
"errors"
2020-11-17 15:04:04 +03:00
"fmt"
2019-03-14 11:30:04 +03:00
"io"
"net"
2022-09-14 12:50:08 +03:00
"syscall"
2019-09-13 18:46:04 +03:00
"time"
2019-03-14 11:30:04 +03:00
2020-11-17 15:04:04 +03:00
"github.com/pires/go-proxyproto"
"github.com/traefik/traefik/v2/pkg/config/dynamic"
2020-09-16 16:46:04 +03:00
"github.com/traefik/traefik/v2/pkg/log"
2019-03-14 11:30:04 +03:00
)
2020-05-11 13:06:07 +03:00
// Proxy forwards a TCP request to a TCP service.
2019-03-14 11:30:04 +03:00
type Proxy struct {
2020-11-13 14:48:04 +03:00
address string
2022-05-19 17:40:09 +03:00
tcpAddr * net . TCPAddr
2019-09-13 18:46:04 +03:00
terminationDelay time . Duration
2020-11-17 15:04:04 +03:00
proxyProtocol * dynamic . ProxyProtocol
2019-03-14 11:30:04 +03:00
}
2020-05-11 13:06:07 +03:00
// NewProxy creates a new Proxy.
2020-11-17 15:04:04 +03:00
func NewProxy ( address string , terminationDelay time . Duration , proxyProtocol * dynamic . ProxyProtocol ) ( * Proxy , error ) {
if proxyProtocol != nil && ( proxyProtocol . Version < 1 || proxyProtocol . Version > 2 ) {
return nil , fmt . Errorf ( "unknown proxyProtocol version: %d" , proxyProtocol . Version )
}
2022-05-19 17:40:09 +03:00
// Creates the tcpAddr only for IP based addresses,
// because there is no need to resolve the name on every new connection,
// and building it should happen once.
var tcpAddr * net . TCPAddr
if host , _ , err := net . SplitHostPort ( address ) ; err == nil && net . ParseIP ( host ) != nil {
tcpAddr , err = net . ResolveTCPAddr ( "tcp" , address )
if err != nil {
return nil , err
}
2020-11-13 14:48:04 +03:00
}
return & Proxy {
address : address ,
2022-05-19 17:40:09 +03:00
tcpAddr : tcpAddr ,
2020-11-13 14:48:04 +03:00
terminationDelay : terminationDelay ,
2020-11-20 13:30:07 +03:00
proxyProtocol : proxyProtocol ,
2020-11-13 14:48:04 +03:00
} , nil
2019-03-14 11:30:04 +03:00
}
2020-05-11 13:06:07 +03:00
// ServeTCP forwards the connection to a service.
2019-09-13 18:46:04 +03:00
func ( p * Proxy ) ServeTCP ( conn WriteCloser ) {
2022-05-30 12:14:09 +03:00
log . WithoutContext ( ) . Debugf ( "Handling connection from %s to %s" , conn . RemoteAddr ( ) , p . address )
2019-09-13 18:46:04 +03:00
// needed because of e.g. server.trackedConnection
2019-03-14 11:30:04 +03:00
defer conn . Close ( )
2019-05-09 15:30:06 +03:00
2021-03-23 13:24:03 +03:00
connBackend , err := p . dialBackend ( )
2019-03-14 11:30:04 +03:00
if err != nil {
2021-03-23 13:24:03 +03:00
log . WithoutContext ( ) . Errorf ( "Error while connecting to backend: %v" , err )
2019-03-14 11:30:04 +03:00
return
}
2019-09-13 18:46:04 +03:00
// maybe not needed, but just in case
2019-03-14 11:30:04 +03:00
defer connBackend . Close ( )
2019-09-13 18:46:04 +03:00
errChan := make ( chan error )
2020-11-17 15:04:04 +03:00
if p . proxyProtocol != nil && p . proxyProtocol . Version > 0 && p . proxyProtocol . Version < 3 {
header := proxyproto . HeaderProxyFromAddrs ( byte ( p . proxyProtocol . Version ) , conn . RemoteAddr ( ) , conn . LocalAddr ( ) )
if _ , err := header . WriteTo ( connBackend ) ; err != nil {
log . WithoutContext ( ) . Errorf ( "Error while writing proxy protocol headers to backend connection: %v" , err )
return
}
}
2019-09-13 18:46:04 +03:00
go p . connCopy ( conn , connBackend , errChan )
go p . connCopy ( connBackend , conn , errChan )
2019-03-14 11:30:04 +03:00
err = <- errChan
if err != nil {
2022-09-14 12:50:08 +03:00
// 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 )
}
2019-03-14 11:30:04 +03:00
}
2019-09-13 18:46:04 +03:00
<- errChan
2019-03-14 11:30:04 +03:00
}
2021-03-23 13:24:03 +03:00
func ( p Proxy ) dialBackend ( ) ( * net . TCPConn , error ) {
2022-05-19 17:40:09 +03:00
// Dial using directly the TCPAddr for IP based addresses.
if p . tcpAddr != nil {
return net . DialTCP ( "tcp" , nil , p . tcpAddr )
2021-03-23 13:24:03 +03:00
}
2022-05-19 17:40:09 +03:00
log . WithoutContext ( ) . Debugf ( "Dial with lookup to address %s" , p . address )
// Dial with DNS lookup for host based addresses.
2021-03-23 13:24:03 +03:00
conn , err := net . Dial ( "tcp" , p . address )
if err != nil {
return nil , err
}
return conn . ( * net . TCPConn ) , nil
}
2019-09-13 18:46:04 +03:00
func ( p Proxy ) connCopy ( dst , src WriteCloser , errCh chan error ) {
2019-03-14 11:30:04 +03:00
_ , err := io . Copy ( dst , src )
errCh <- err
2019-09-13 18:46:04 +03:00
2022-09-14 12:50:08 +03:00
// Ends the connection with the dst connection peer.
// It corresponds to sending a FIN packet to gracefully end the TCP session.
2019-09-13 18:46:04 +03:00
errClose := dst . CloseWrite ( )
if errClose != nil {
2022-09-14 12:50:08 +03:00
// 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 )
}
2019-09-13 21:00:06 +03:00
return
2019-09-13 18:46:04 +03:00
}
if p . terminationDelay >= 0 {
err := dst . SetReadDeadline ( time . Now ( ) . Add ( p . terminationDelay ) )
if err != nil {
2019-09-13 21:00:06 +03:00
log . WithoutContext ( ) . Debugf ( "Error while setting deadline: %v" , err )
2019-09-13 18:46:04 +03:00
}
}
2019-03-14 11:30:04 +03:00
}
2022-09-14 12:50:08 +03:00
// 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 )
}