2019-03-14 08:30:04 +00:00
package tcp
import (
"context"
2019-09-13 18:00:06 +00:00
"errors"
2019-03-14 08:30:04 +00:00
"fmt"
2022-09-14 12:42:08 +00:00
"math/rand"
2019-03-14 08:30:04 +00:00
"net"
2019-09-13 15:46:04 +00:00
"time"
2019-03-14 08:30:04 +00:00
2022-11-21 17:36:05 +00:00
"github.com/rs/zerolog/log"
2023-02-03 14:24:05 +00:00
"github.com/traefik/traefik/v3/pkg/config/runtime"
"github.com/traefik/traefik/v3/pkg/logs"
"github.com/traefik/traefik/v3/pkg/server/provider"
"github.com/traefik/traefik/v3/pkg/tcp"
2024-01-29 16:32:05 +00:00
"golang.org/x/net/proxy"
2019-03-14 08:30:04 +00:00
)
2020-05-11 10:06:07 +00:00
// Manager is the TCPHandlers factory.
2019-03-14 08:30:04 +00:00
type Manager struct {
2022-12-09 08:58:05 +00:00
dialerManager * tcp . DialerManager
configs map [ string ] * runtime . TCPServiceInfo
rand * rand . Rand // For the initial shuffling of load-balancers.
2019-03-14 08:30:04 +00:00
}
2020-05-11 10:06:07 +00:00
// NewManager creates a new manager.
2022-12-09 08:58:05 +00:00
func NewManager ( conf * runtime . Configuration , dialerManager * tcp . DialerManager ) * Manager {
2019-03-14 08:30:04 +00:00
return & Manager {
2022-12-09 08:58:05 +00:00
dialerManager : dialerManager ,
configs : conf . TCPServices ,
rand : rand . New ( rand . NewSource ( time . Now ( ) . UnixNano ( ) ) ) ,
2019-03-14 08:30:04 +00:00
}
}
// BuildTCP Creates a tcp.Handler for a service configuration.
func ( m * Manager ) BuildTCP ( rootCtx context . Context , serviceName string ) ( tcp . Handler , error ) {
2020-01-27 09:40:05 +00:00
serviceQualifiedName := provider . GetQualifiedName ( rootCtx , serviceName )
2022-11-21 17:36:05 +00:00
logger := log . Ctx ( rootCtx ) . With ( ) . Str ( logs . ServiceName , serviceQualifiedName ) . Logger ( )
2020-01-27 09:40:05 +00:00
ctx := provider . AddInContext ( rootCtx , serviceQualifiedName )
2019-05-09 12:30:06 +00:00
conf , ok := m . configs [ serviceQualifiedName ]
if ! ok {
2019-05-16 08:58:06 +00:00
return nil , fmt . Errorf ( "the service %q does not exist" , serviceQualifiedName )
2019-05-09 12:30:06 +00:00
}
2019-09-13 18:00:06 +00:00
if conf . LoadBalancer != nil && conf . Weighted != nil {
err := errors . New ( "cannot create service: multi-types service not supported, consider declaring two different pieces of service instead" )
2019-07-19 14:42:04 +00:00
conf . AddError ( err , true )
return nil , err
2019-05-09 12:30:06 +00:00
}
2019-09-13 18:00:06 +00:00
switch {
case conf . LoadBalancer != nil :
loadBalancer := tcp . NewWRRLoadBalancer ( )
2019-05-09 12:30:06 +00:00
2024-01-29 16:32:05 +00:00
if conf . LoadBalancer . TerminationDelay != nil {
log . Ctx ( ctx ) . Warn ( ) . Msgf ( "Service %q load balancer uses `TerminationDelay`, but this option is deprecated, please use ServersTransport configuration instead." , serviceName )
}
2022-12-09 08:58:05 +00:00
if len ( conf . LoadBalancer . ServersTransport ) > 0 {
conf . LoadBalancer . ServersTransport = provider . GetQualifiedName ( ctx , conf . LoadBalancer . ServersTransport )
2019-09-13 18:00:06 +00:00
}
2019-05-09 12:30:06 +00:00
2022-11-21 17:36:05 +00:00
for index , server := range shuffle ( conf . LoadBalancer . Servers , m . rand ) {
srvLogger := logger . With ( ) .
Int ( logs . ServerIndex , index ) .
Str ( "serverAddress" , server . Address ) . Logger ( )
2019-09-13 18:00:06 +00:00
if _ , _ , err := net . SplitHostPort ( server . Address ) ; err != nil {
2022-11-21 17:36:05 +00:00
srvLogger . Error ( ) . Err ( err ) . Msg ( "Failed to split host port" )
2019-09-13 18:00:06 +00:00
continue
}
2019-09-13 15:46:04 +00:00
2022-12-09 08:58:05 +00:00
dialer , err := m . dialerManager . Get ( conf . LoadBalancer . ServersTransport , server . TLS )
if err != nil {
return nil , err
}
2024-01-29 16:32:05 +00:00
// Handle TerminationDelay deprecated option.
if conf . LoadBalancer . ServersTransport == "" && conf . LoadBalancer . TerminationDelay != nil {
dialer = & dialerWrapper {
Dialer : dialer ,
terminationDelay : time . Duration ( * conf . LoadBalancer . TerminationDelay ) ,
}
}
2022-12-09 08:58:05 +00:00
handler , err := tcp . NewProxy ( server . Address , conf . LoadBalancer . ProxyProtocol , dialer )
2019-09-13 18:00:06 +00:00
if err != nil {
2022-11-21 17:36:05 +00:00
srvLogger . Error ( ) . Err ( err ) . Msg ( "Failed to create server" )
2019-09-13 18:00:06 +00:00
continue
}
2019-05-09 12:30:06 +00:00
2019-09-13 18:00:06 +00:00
loadBalancer . AddServer ( handler )
2022-11-21 17:36:05 +00:00
logger . Debug ( ) . Msg ( "Creating TCP server" )
2019-03-14 08:30:04 +00:00
}
2022-11-21 17:36:05 +00:00
2019-09-13 18:00:06 +00:00
return loadBalancer , nil
2022-11-21 17:36:05 +00:00
2019-09-13 18:00:06 +00:00
case conf . Weighted != nil :
loadBalancer := tcp . NewWRRLoadBalancer ( )
2022-09-14 12:42:08 +00:00
for _ , service := range shuffle ( conf . Weighted . Services , m . rand ) {
2022-11-21 17:36:05 +00:00
handler , err := m . BuildTCP ( ctx , service . Name )
2019-09-13 18:00:06 +00:00
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Err ( err ) . Msg ( "Failed to build TCP handler" )
2019-09-13 18:00:06 +00:00
return nil , err
}
2022-11-21 17:36:05 +00:00
2019-09-13 18:00:06 +00:00
loadBalancer . AddWeightServer ( handler , service . Weight )
}
2022-11-21 17:36:05 +00:00
2019-09-13 18:00:06 +00:00
return loadBalancer , nil
2022-11-21 17:36:05 +00:00
2019-09-13 18:00:06 +00:00
default :
2020-02-11 00:26:04 +00:00
err := fmt . Errorf ( "the service %q does not have any type defined" , serviceQualifiedName )
2019-09-13 18:00:06 +00:00
conf . AddError ( err , true )
return nil , err
2019-03-14 08:30:04 +00:00
}
}
2022-09-14 12:42:08 +00:00
func shuffle [ T any ] ( values [ ] T , r * rand . Rand ) [ ] T {
shuffled := make ( [ ] T , len ( values ) )
copy ( shuffled , values )
r . Shuffle ( len ( shuffled ) , func ( i , j int ) { shuffled [ i ] , shuffled [ j ] = shuffled [ j ] , shuffled [ i ] } )
return shuffled
}
2024-01-29 16:32:05 +00:00
// dialerWrapper is only used to handle TerminationDelay deprecated option on TCPServersLoadBalancer.
type dialerWrapper struct {
proxy . Dialer
terminationDelay time . Duration
}
func ( d dialerWrapper ) TerminationDelay ( ) time . Duration {
return d . terminationDelay
}