TUN-9470: Add OriginDialerService to include TCP

Adds an OriginDialerService that takes over the roles of both DialUDP and DialTCP 
towards the origin. This provides the possibility to leverage dialer "middleware"
to inject virtual origins, such as the DNS resolver service.

DNS Resolver service also gains access to the DialTCP operation to service TCP
DNS requests.

Minor refactoring includes changes to remove the needs previously provided by
the warp-routing configuration. This configuration cannot be disabled by cloudflared
so many of the references have been adjusted or removed.

Closes TUN-9470
This commit is contained in:
Devin Carr
2025-06-30 13:24:16 -07:00
parent 9ca8b41cf7
commit 70ed7ffc5f
19 changed files with 503 additions and 254 deletions

View File

@@ -220,7 +220,15 @@ func prepareTunnelConfig(
resolvedRegion = endpoint resolvedRegion = endpoint
} }
dnsService := origins.NewDNSResolver(log) warpRoutingConfig := ingress.NewWarpRoutingConfig(&cfg.WarpRouting)
// Setup origin dialer service and virtual services
originDialerService := ingress.NewOriginDialer(ingress.OriginConfig{
DefaultDialer: ingress.NewDialer(warpRoutingConfig),
TCPWriteTimeout: c.Duration(flags.WriteStreamTimeout),
}, log)
dnsService := origins.NewDNSResolverService(origins.NewDNSDialer(), log)
originDialerService.AddReservedService(dnsService, []netip.AddrPort{origins.VirtualDNSServiceAddr})
tunnelConfig := &supervisor.TunnelConfig{ tunnelConfig := &supervisor.TunnelConfig{
ClientConfig: clientConfig, ClientConfig: clientConfig,
@@ -250,6 +258,7 @@ func prepareTunnelConfig(
QUICConnectionLevelFlowControlLimit: c.Uint64(flags.QuicConnLevelFlowControlLimit), QUICConnectionLevelFlowControlLimit: c.Uint64(flags.QuicConnLevelFlowControlLimit),
QUICStreamLevelFlowControlLimit: c.Uint64(flags.QuicStreamLevelFlowControlLimit), QUICStreamLevelFlowControlLimit: c.Uint64(flags.QuicStreamLevelFlowControlLimit),
OriginDNSService: dnsService, OriginDNSService: dnsService,
OriginDialerService: originDialerService,
} }
icmpRouter, err := newICMPRouter(c, log) icmpRouter, err := newICMPRouter(c, log)
if err != nil { if err != nil {
@@ -258,10 +267,10 @@ func prepareTunnelConfig(
tunnelConfig.ICMPRouterServer = icmpRouter tunnelConfig.ICMPRouterServer = icmpRouter
} }
orchestratorConfig := &orchestration.Config{ orchestratorConfig := &orchestration.Config{
Ingress: &ingressRules, Ingress: &ingressRules,
WarpRouting: ingress.NewWarpRoutingConfig(&cfg.WarpRouting), WarpRouting: warpRoutingConfig,
ConfigurationFlags: parseConfigFlags(c), OriginDialerService: originDialerService,
WriteTimeout: tunnelConfig.WriteStreamTimeout, ConfigurationFlags: parseConfigFlags(c),
} }
return tunnelConfig, orchestratorConfig, nil return tunnelConfig, orchestratorConfig, nil
} }

View File

@@ -30,6 +30,7 @@ import (
"golang.org/x/net/nettest" "golang.org/x/net/nettest"
"github.com/cloudflare/cloudflared/client" "github.com/cloudflare/cloudflared/client"
"github.com/cloudflare/cloudflared/config"
cfdflow "github.com/cloudflare/cloudflared/flow" cfdflow "github.com/cloudflare/cloudflared/flow"
"github.com/cloudflare/cloudflared/datagramsession" "github.com/cloudflare/cloudflared/datagramsession"
@@ -823,6 +824,15 @@ func testTunnelConnection(t *testing.T, serverAddr netip.AddrPort, index uint8)
sessionManager := datagramsession.NewManager(&log, datagramMuxer.SendToSession, sessionDemuxChan) sessionManager := datagramsession.NewManager(&log, datagramMuxer.SendToSession, sessionDemuxChan)
var connIndex uint8 = 0 var connIndex uint8 = 0
packetRouter := ingress.NewPacketRouter(nil, datagramMuxer, connIndex, &log) packetRouter := ingress.NewPacketRouter(nil, datagramMuxer, connIndex, &log)
testDefaultDialer := ingress.NewDialer(ingress.WarpRoutingConfig{
ConnectTimeout: config.CustomDuration{Duration: 1 * time.Second},
TCPKeepAlive: config.CustomDuration{Duration: 15 * time.Second},
MaxActiveFlows: 0,
})
originDialer := ingress.NewOriginDialer(ingress.OriginConfig{
DefaultDialer: testDefaultDialer,
TCPWriteTimeout: 1 * time.Second,
}, &log)
datagramConn := &datagramV2Connection{ datagramConn := &datagramV2Connection{
conn, conn,
@@ -830,7 +840,7 @@ func testTunnelConnection(t *testing.T, serverAddr netip.AddrPort, index uint8)
sessionManager, sessionManager,
cfdflow.NewLimiter(0), cfdflow.NewLimiter(0),
datagramMuxer, datagramMuxer,
ingress.DefaultUDPDialer, originDialer,
packetRouter, packetRouter,
15 * time.Second, 15 * time.Second,
0 * time.Second, 0 * time.Second,

View File

@@ -57,8 +57,8 @@ type datagramV2Connection struct {
// datagramMuxer mux/demux datagrams from quic connection // datagramMuxer mux/demux datagrams from quic connection
datagramMuxer *cfdquic.DatagramMuxerV2 datagramMuxer *cfdquic.DatagramMuxerV2
// ingressUDPProxy acts as the origin dialer for UDP requests // originDialer is the origin dialer for UDP requests
ingressUDPProxy ingress.UDPOriginProxy originDialer ingress.OriginUDPDialer
// packetRouter acts as the origin router for ICMP requests // packetRouter acts as the origin router for ICMP requests
packetRouter *ingress.PacketRouter packetRouter *ingress.PacketRouter
@@ -70,7 +70,7 @@ type datagramV2Connection struct {
func NewDatagramV2Connection(ctx context.Context, func NewDatagramV2Connection(ctx context.Context,
conn quic.Connection, conn quic.Connection,
ingressUDPProxy ingress.UDPOriginProxy, originDialer ingress.OriginUDPDialer,
icmpRouter ingress.ICMPRouter, icmpRouter ingress.ICMPRouter,
index uint8, index uint8,
rpcTimeout time.Duration, rpcTimeout time.Duration,
@@ -89,7 +89,7 @@ func NewDatagramV2Connection(ctx context.Context,
sessionManager: sessionManager, sessionManager: sessionManager,
flowLimiter: flowLimiter, flowLimiter: flowLimiter,
datagramMuxer: datagramMuxer, datagramMuxer: datagramMuxer,
ingressUDPProxy: ingressUDPProxy, originDialer: originDialer,
packetRouter: packetRouter, packetRouter: packetRouter,
rpcTimeout: rpcTimeout, rpcTimeout: rpcTimeout,
streamWriteTimeout: streamWriteTimeout, streamWriteTimeout: streamWriteTimeout,
@@ -159,7 +159,7 @@ func (q *datagramV2Connection) RegisterUdpSession(ctx context.Context, sessionID
// Each session is a series of datagram from an eyeball to a dstIP:dstPort. // Each session is a series of datagram from an eyeball to a dstIP:dstPort.
// (src port, dst IP, dst port) uniquely identifies a session, so it needs a dedicated connected socket. // (src port, dst IP, dst port) uniquely identifies a session, so it needs a dedicated connected socket.
originProxy, err := q.ingressUDPProxy.DialUDP(dstAddrPort) originProxy, err := q.originDialer.DialUDP(dstAddrPort)
if err != nil { if err != nil {
log.Err(err).Msgf("Failed to create udp proxy to %s", dstAddrPort) log.Err(err).Msgf("Failed to create udp proxy to %s", dstAddrPort)
tracing.EndWithErrorStatus(registerSpan, err) tracing.EndWithErrorStatus(registerSpan, err)

View File

@@ -13,7 +13,6 @@ import (
"go.uber.org/mock/gomock" "go.uber.org/mock/gomock"
cfdflow "github.com/cloudflare/cloudflared/flow" cfdflow "github.com/cloudflare/cloudflared/flow"
"github.com/cloudflare/cloudflared/ingress"
"github.com/cloudflare/cloudflared/mocks" "github.com/cloudflare/cloudflared/mocks"
) )
@@ -84,7 +83,7 @@ func TestRateLimitOnNewDatagramV2UDPSession(t *testing.T) {
datagramConn := NewDatagramV2Connection( datagramConn := NewDatagramV2Connection(
t.Context(), t.Context(),
conn, conn,
ingress.DefaultUDPDialer, nil,
nil, nil,
0, 0,
0*time.Second, 0*time.Second,

View File

@@ -19,7 +19,7 @@ import (
type OriginConnection interface { type OriginConnection interface {
// Stream should generally be implemented as a bidirectional io.Copy. // Stream should generally be implemented as a bidirectional io.Copy.
Stream(ctx context.Context, tunnelConn io.ReadWriter, log *zerolog.Logger) Stream(ctx context.Context, tunnelConn io.ReadWriter, log *zerolog.Logger)
Close() Close() error
} }
type streamHandlerFunc func(originConn io.ReadWriter, remoteConn net.Conn, log *zerolog.Logger) type streamHandlerFunc func(originConn io.ReadWriter, remoteConn net.Conn, log *zerolog.Logger)
@@ -48,16 +48,7 @@ func (tc *tcpConnection) Write(b []byte) (int, error) {
} }
} }
nBytes, err := tc.Conn.Write(b) return tc.Conn.Write(b)
if err != nil {
tc.logger.Err(err).Msg("Error writing to the TCP connection")
}
return nBytes, err
}
func (tc *tcpConnection) Close() {
tc.Conn.Close()
} }
// tcpOverWSConnection is an OriginConnection that streams to TCP over WS. // tcpOverWSConnection is an OriginConnection that streams to TCP over WS.
@@ -75,8 +66,8 @@ func (wc *tcpOverWSConnection) Stream(ctx context.Context, tunnelConn io.ReadWri
wsConn.Close() wsConn.Close()
} }
func (wc *tcpOverWSConnection) Close() { func (wc *tcpOverWSConnection) Close() error {
wc.conn.Close() return wc.conn.Close()
} }
// socksProxyOverWSConnection is an OriginConnection that streams SOCKS connections over WS. // socksProxyOverWSConnection is an OriginConnection that streams SOCKS connections over WS.
@@ -95,5 +86,6 @@ func (sp *socksProxyOverWSConnection) Stream(ctx context.Context, tunnelConn io.
wsConn.Close() wsConn.Close()
} }
func (sp *socksProxyOverWSConnection) Close() { func (sp *socksProxyOverWSConnection) Close() error {
return nil
} }

146
ingress/origin_dialer.go Normal file
View File

@@ -0,0 +1,146 @@
package ingress
import (
"context"
"fmt"
"net"
"net/netip"
"sync"
"time"
"github.com/rs/zerolog"
)
// OriginTCPDialer provides a TCP dial operation to a requested address.
type OriginTCPDialer interface {
DialTCP(ctx context.Context, addr netip.AddrPort) (net.Conn, error)
}
// OriginUDPDialer provides a UDP dial operation to a requested address.
type OriginUDPDialer interface {
DialUDP(addr netip.AddrPort) (net.Conn, error)
}
// OriginDialer provides both TCP and UDP dial operations to an address.
type OriginDialer interface {
OriginTCPDialer
OriginUDPDialer
}
type OriginConfig struct {
// The default Dialer used if no reserved services are found for an origin request.
DefaultDialer OriginDialer
// Timeout on write operations for TCP connections to the origin.
TCPWriteTimeout time.Duration
}
// OriginDialerService provides a proxy TCP and UDP dialer to origin services while allowing reserved
// services to be provided. These reserved services are assigned to specific [netip.AddrPort]s
// and provide their own [OriginDialer]'s to handle origin dialing per protocol.
type OriginDialerService struct {
// Reserved TCP services for reserved AddrPort values
reservedTCPServices map[netip.AddrPort]OriginTCPDialer
// Reserved UDP services for reserved AddrPort values
reservedUDPServices map[netip.AddrPort]OriginUDPDialer
// The default Dialer used if no reserved services are found for an origin request
defaultDialer OriginDialer
defaultDialerM sync.RWMutex
// Write timeout for TCP connections
writeTimeout time.Duration
logger *zerolog.Logger
}
func NewOriginDialer(config OriginConfig, logger *zerolog.Logger) *OriginDialerService {
return &OriginDialerService{
reservedTCPServices: map[netip.AddrPort]OriginTCPDialer{},
reservedUDPServices: map[netip.AddrPort]OriginUDPDialer{},
defaultDialer: config.DefaultDialer,
writeTimeout: config.TCPWriteTimeout,
logger: logger,
}
}
// AddReservedService adds a reserved virtual service to dial to.
// Not locked and expected to be initialized before calling first dial and not afterwards.
func (d *OriginDialerService) AddReservedService(service OriginDialer, addrs []netip.AddrPort) {
for _, addr := range addrs {
d.reservedTCPServices[addr] = service
d.reservedUDPServices[addr] = service
}
}
// UpdateDefaultDialer updates the default dialer.
func (d *OriginDialerService) UpdateDefaultDialer(dialer *Dialer) {
d.defaultDialerM.Lock()
defer d.defaultDialerM.Unlock()
d.defaultDialer = dialer
}
// DialTCP will perform a dial TCP to the requested addr.
func (d *OriginDialerService) DialTCP(ctx context.Context, addr netip.AddrPort) (net.Conn, error) {
conn, err := d.dialTCP(ctx, addr)
if err != nil {
return nil, err
}
// Assign the write timeout for the TCP operations
return &tcpConnection{
Conn: conn,
writeTimeout: d.writeTimeout,
logger: d.logger,
}, nil
}
func (d *OriginDialerService) dialTCP(ctx context.Context, addr netip.AddrPort) (net.Conn, error) {
// Check to see if any reserved services are available for this addr and call their dialer instead.
if dialer, ok := d.reservedTCPServices[addr]; ok {
return dialer.DialTCP(ctx, addr)
}
d.defaultDialerM.RLock()
dialer := d.defaultDialer
d.defaultDialerM.RUnlock()
return dialer.DialTCP(ctx, addr)
}
// DialUDP will perform a dial UDP to the requested addr.
func (d *OriginDialerService) DialUDP(addr netip.AddrPort) (net.Conn, error) {
// Check to see if any reserved services are available for this addr and call their dialer instead.
if dialer, ok := d.reservedUDPServices[addr]; ok {
return dialer.DialUDP(addr)
}
d.defaultDialerM.RLock()
dialer := d.defaultDialer
d.defaultDialerM.RUnlock()
return dialer.DialUDP(addr)
}
type Dialer struct {
Dialer net.Dialer
}
func NewDialer(config WarpRoutingConfig) *Dialer {
return &Dialer{
Dialer: net.Dialer{
Timeout: config.ConnectTimeout.Duration,
KeepAlive: config.TCPKeepAlive.Duration,
},
}
}
func (d *Dialer) DialTCP(ctx context.Context, dest netip.AddrPort) (net.Conn, error) {
conn, err := d.Dialer.DialContext(ctx, "tcp", dest.String())
if err != nil {
return nil, fmt.Errorf("unable to dial tcp to origin %s: %w", dest, err)
}
return conn, nil
}
func (d *Dialer) DialUDP(dest netip.AddrPort) (net.Conn, error) {
conn, err := d.Dialer.Dial("udp", dest.String())
if err != nil {
return nil, fmt.Errorf("unable to dial udp to origin %s: %w", dest, err)
}
return conn, nil
}

View File

@@ -1,66 +0,0 @@
package ingress
import (
"fmt"
"net"
"net/netip"
"github.com/rs/zerolog"
)
// UDPOriginService provides a proxy UDP dialer to origin services while allowing reserved
// services to be provided. These reserved services are assigned to specific [netip.AddrPort]s
// and provide their own [UDPOriginProxy]s to handle UDP origin dialing.
type UDPOriginService struct {
// Reserved services for reserved AddrPort values
reservedServices map[netip.AddrPort]UDPOriginProxy
// The default UDP Dialer used if no reserved services are found for an origin request.
defaultDialer UDPOriginProxy
logger *zerolog.Logger
}
// UDPOriginProxy provides a UDP dial operation to a requested addr.
type UDPOriginProxy interface {
DialUDP(addr netip.AddrPort) (net.Conn, error)
}
func NewUDPOriginService(reserved map[netip.AddrPort]UDPOriginProxy, logger *zerolog.Logger) *UDPOriginService {
return &UDPOriginService{
reservedServices: reserved,
defaultDialer: DefaultUDPDialer,
logger: logger,
}
}
// SetUDPDialer updates the default UDP Dialer used.
// Typically used in unit testing.
func (s *UDPOriginService) SetDefaultDialer(dialer UDPOriginProxy) {
s.defaultDialer = dialer
}
// DialUDP will perform a dial UDP to the requested addr.
func (s *UDPOriginService) DialUDP(addr netip.AddrPort) (net.Conn, error) {
// Check to see if any reserved services are available for this addr and call their dialer instead.
if dialer, ok := s.reservedServices[addr]; ok {
return dialer.DialUDP(addr)
}
return s.defaultDialer.DialUDP(addr)
}
type defaultUDPDialer struct{}
var DefaultUDPDialer UDPOriginProxy = &defaultUDPDialer{}
func (d *defaultUDPDialer) DialUDP(dest netip.AddrPort) (net.Conn, error) {
addr := net.UDPAddrFromAddrPort(dest)
// We use nil as local addr to force runtime to find the best suitable local address IP given the destination
// address as context.
udpConn, err := net.DialUDP("udp", nil, addr)
if err != nil {
return nil, fmt.Errorf("unable to dial udp to origin %s: %w", dest, err)
}
return udpConn, nil
}

View File

@@ -45,20 +45,28 @@ type DNSResolverService struct {
address netip.AddrPort address netip.AddrPort
addressM sync.RWMutex addressM sync.RWMutex
dialer ingress.UDPOriginProxy dialer ingress.OriginDialer
resolver peekResolver resolver peekResolver
logger *zerolog.Logger logger *zerolog.Logger
} }
func NewDNSResolver(logger *zerolog.Logger) *DNSResolverService { func NewDNSResolverService(dialer ingress.OriginDialer, logger *zerolog.Logger) *DNSResolverService {
return &DNSResolverService{ return &DNSResolverService{
address: defaultResolverAddr, address: defaultResolverAddr,
dialer: ingress.DefaultUDPDialer, dialer: dialer,
resolver: &resolver{dialFunc: net.Dial}, resolver: &resolver{dialFunc: net.Dial},
logger: logger, logger: logger,
} }
} }
func (s *DNSResolverService) DialTCP(ctx context.Context, _ netip.AddrPort) (net.Conn, error) {
s.addressM.RLock()
dest := s.address
s.addressM.RUnlock()
// The dialer ignores the provided address because the request will instead go to the local DNS resolver.
return s.dialer.DialTCP(ctx, dest)
}
func (s *DNSResolverService) DialUDP(_ netip.AddrPort) (net.Conn, error) { func (s *DNSResolverService) DialUDP(_ netip.AddrPort) (net.Conn, error) {
s.addressM.RLock() s.addressM.RLock()
dest := s.address dest := s.address
@@ -155,3 +163,18 @@ func (r *resolver) peekDial(ctx context.Context, network, address string) (net.C
r.address = address r.address = address
return r.dialFunc(network, address) return r.dialFunc(network, address)
} }
// NewDNSDialer creates a custom dialer for the DNS resolver service to utilize.
func NewDNSDialer() *ingress.Dialer {
return &ingress.Dialer{
Dialer: net.Dialer{
// We want short timeouts for the DNS requests
Timeout: 5 * time.Second,
// We do not want keep alive since the edge will not reuse TCP connections per request
KeepAlive: -1,
KeepAliveConfig: net.KeepAliveConfig{
Enable: false,
},
},
}
}

View File

@@ -12,7 +12,7 @@ import (
func TestDNSResolver_DefaultResolver(t *testing.T) { func TestDNSResolver_DefaultResolver(t *testing.T) {
log := zerolog.Nop() log := zerolog.Nop()
service := NewDNSResolver(&log) service := NewDNSResolverService(NewDNSDialer(), &log)
mockResolver := &mockPeekResolver{ mockResolver := &mockPeekResolver{
address: "127.0.0.2:53", address: "127.0.0.2:53",
} }
@@ -24,7 +24,7 @@ func TestDNSResolver_DefaultResolver(t *testing.T) {
func TestDNSResolver_UpdateResolverAddress(t *testing.T) { func TestDNSResolver_UpdateResolverAddress(t *testing.T) {
log := zerolog.Nop() log := zerolog.Nop()
service := NewDNSResolver(&log) service := NewDNSResolverService(NewDNSDialer(), &log)
mockResolver := &mockPeekResolver{} mockResolver := &mockPeekResolver{}
service.resolver = mockResolver service.resolver = mockResolver
@@ -51,7 +51,7 @@ func TestDNSResolver_UpdateResolverAddress(t *testing.T) {
func TestDNSResolver_UpdateResolverAddressInvalid(t *testing.T) { func TestDNSResolver_UpdateResolverAddressInvalid(t *testing.T) {
log := zerolog.Nop() log := zerolog.Nop()
service := NewDNSResolver(&log) service := NewDNSResolverService(NewDNSDialer(), &log)
mockResolver := &mockPeekResolver{} mockResolver := &mockPeekResolver{}
service.resolver = mockResolver service.resolver = mockResolver
@@ -77,7 +77,7 @@ func TestDNSResolver_UpdateResolverAddressInvalid(t *testing.T) {
func TestDNSResolver_UpdateResolverErrorIgnored(t *testing.T) { func TestDNSResolver_UpdateResolverErrorIgnored(t *testing.T) {
log := zerolog.Nop() log := zerolog.Nop()
service := NewDNSResolver(&log) service := NewDNSResolverService(NewDNSDialer(), &log)
resolverErr := errors.New("test resolver error") resolverErr := errors.New("test resolver error")
mockResolver := &mockPeekResolver{err: resolverErr} mockResolver := &mockPeekResolver{err: resolverErr}
service.resolver = mockResolver service.resolver = mockResolver
@@ -93,13 +93,12 @@ func TestDNSResolver_UpdateResolverErrorIgnored(t *testing.T) {
} }
} }
func TestDNSResolver_DialUsesResolvedAddress(t *testing.T) { func TestDNSResolver_DialUDPUsesResolvedAddress(t *testing.T) {
log := zerolog.Nop() log := zerolog.Nop()
service := NewDNSResolver(&log) mockDialer := &mockDialer{expected: defaultResolverAddr}
service := NewDNSResolverService(mockDialer, &log)
mockResolver := &mockPeekResolver{} mockResolver := &mockPeekResolver{}
service.resolver = mockResolver service.resolver = mockResolver
mockDialer := &mockDialer{expected: defaultResolverAddr}
service.dialer = mockDialer
// Attempt a dial to 127.0.0.2:53 which should be ignored and instead resolve to 127.0.0.1:53 // Attempt a dial to 127.0.0.2:53 which should be ignored and instead resolve to 127.0.0.1:53
_, err := service.DialUDP(netip.MustParseAddrPort("127.0.0.2:53")) _, err := service.DialUDP(netip.MustParseAddrPort("127.0.0.2:53"))
@@ -108,6 +107,20 @@ func TestDNSResolver_DialUsesResolvedAddress(t *testing.T) {
} }
} }
func TestDNSResolver_DialTCPUsesResolvedAddress(t *testing.T) {
log := zerolog.Nop()
mockDialer := &mockDialer{expected: defaultResolverAddr}
service := NewDNSResolverService(mockDialer, &log)
mockResolver := &mockPeekResolver{}
service.resolver = mockResolver
// Attempt a dial to 127.0.0.2:53 which should be ignored and instead resolve to 127.0.0.1:53
_, err := service.DialTCP(t.Context(), netip.MustParseAddrPort("127.0.0.2:53"))
if err != nil {
t.Error(err)
}
}
type mockPeekResolver struct { type mockPeekResolver struct {
err error err error
address string address string
@@ -126,6 +139,13 @@ type mockDialer struct {
expected netip.AddrPort expected netip.AddrPort
} }
func (d *mockDialer) DialTCP(ctx context.Context, addr netip.AddrPort) (net.Conn, error) {
if d.expected != addr {
return nil, errors.New("unexpected address dialed")
}
return nil, nil
}
func (d *mockDialer) DialUDP(addr netip.AddrPort) (net.Conn, error) { func (d *mockDialer) DialUDP(addr netip.AddrPort) (net.Conn, error) {
if d.expected != addr { if d.expected != addr {
return nil, errors.New("unexpected address dialed") return nil, errors.New("unexpected address dialed")

View File

@@ -2,7 +2,6 @@ package orchestration
import ( import (
"encoding/json" "encoding/json"
"time"
"github.com/cloudflare/cloudflared/config" "github.com/cloudflare/cloudflared/config"
"github.com/cloudflare/cloudflared/ingress" "github.com/cloudflare/cloudflared/ingress"
@@ -20,9 +19,9 @@ type newLocalConfig struct {
// Config is the original config as read and parsed by cloudflared. // Config is the original config as read and parsed by cloudflared.
type Config struct { type Config struct {
Ingress *ingress.Ingress Ingress *ingress.Ingress
WarpRouting ingress.WarpRoutingConfig WarpRouting ingress.WarpRoutingConfig
WriteTimeout time.Duration OriginDialerService *ingress.OriginDialerService
// Extra settings used to configure this instance but that are not eligible for remotely management // Extra settings used to configure this instance but that are not eligible for remotely management
// ie. (--protocol, --loglevel, ...) // ie. (--protocol, --loglevel, ...)

View File

@@ -38,7 +38,9 @@ type Orchestrator struct {
tags []pogs.Tag tags []pogs.Tag
// flowLimiter tracks active sessions across the tunnel and limits new sessions if they are above the limit. // flowLimiter tracks active sessions across the tunnel and limits new sessions if they are above the limit.
flowLimiter cfdflow.Limiter flowLimiter cfdflow.Limiter
log *zerolog.Logger // Origin dialer service to manage egress socket dialing.
originDialerService *ingress.OriginDialerService
log *zerolog.Logger
// orchestrator must not handle any more updates after shutdownC is closed // orchestrator must not handle any more updates after shutdownC is closed
shutdownC <-chan struct{} shutdownC <-chan struct{}
@@ -50,18 +52,20 @@ func NewOrchestrator(ctx context.Context,
config *Config, config *Config,
tags []pogs.Tag, tags []pogs.Tag,
internalRules []ingress.Rule, internalRules []ingress.Rule,
log *zerolog.Logger) (*Orchestrator, error) { log *zerolog.Logger,
) (*Orchestrator, error) {
o := &Orchestrator{ o := &Orchestrator{
// Lowest possible version, any remote configuration will have version higher than this // Lowest possible version, any remote configuration will have version higher than this
// Starting at -1 allows a configuration migration (local to remote) to override the current configuration as it // Starting at -1 allows a configuration migration (local to remote) to override the current configuration as it
// will start at version 0. // will start at version 0.
currentVersion: -1, currentVersion: -1,
internalRules: internalRules, internalRules: internalRules,
config: config, config: config,
tags: tags, tags: tags,
flowLimiter: cfdflow.NewLimiter(config.WarpRouting.MaxActiveFlows), flowLimiter: cfdflow.NewLimiter(config.WarpRouting.MaxActiveFlows),
log: log, originDialerService: config.OriginDialerService,
shutdownC: ctx.Done(), log: log,
shutdownC: ctx.Done(),
} }
if err := o.updateIngress(*config.Ingress, config.WarpRouting); err != nil { if err := o.updateIngress(*config.Ingress, config.WarpRouting); err != nil {
return nil, err return nil, err
@@ -175,7 +179,15 @@ func (o *Orchestrator) updateIngress(ingressRules ingress.Ingress, warpRouting i
// Update the flow limit since the configuration might have changed // Update the flow limit since the configuration might have changed
o.flowLimiter.SetLimit(warpRouting.MaxActiveFlows) o.flowLimiter.SetLimit(warpRouting.MaxActiveFlows)
proxy := proxy.NewOriginProxy(ingressRules, warpRouting, o.tags, o.flowLimiter, o.config.WriteTimeout, o.log) // Update the origin dialer service with the new dialer settings
// We need to update the dialer here instead of creating a new instance of OriginDialerService because it has
// its own references and go routines. Specifically, the UDP dialer is a reference to this same service all the
// way into the datagram manager. Reconstructing the datagram manager is not something we currently provide during
// runtime in response to a configuration push except when starting a tunnel connection.
o.originDialerService.UpdateDefaultDialer(ingress.NewDialer(warpRouting))
// Create and replace the origin proxy with a new instance
proxy := proxy.NewOriginProxy(ingressRules, o.originDialerService, o.tags, o.flowLimiter, o.log)
o.proxy.Store(proxy) o.proxy.Store(proxy)
o.config.Ingress = &ingressRules o.config.Ingress = &ingressRules
o.config.WarpRouting = warpRouting o.config.WarpRouting = warpRouting

View File

@@ -41,6 +41,11 @@ var (
Value: "test", Value: "test",
}, },
} }
testDefaultDialer = ingress.NewDialer(ingress.WarpRoutingConfig{
ConnectTimeout: config.CustomDuration{Duration: 1 * time.Second},
TCPKeepAlive: config.CustomDuration{Duration: 15 * time.Second},
MaxActiveFlows: 0,
})
) )
// TestUpdateConfiguration tests that // TestUpdateConfiguration tests that
@@ -50,8 +55,13 @@ var (
// - configurations can be deserialized // - configurations can be deserialized
// - receiving an old version is noop // - receiving an old version is noop
func TestUpdateConfiguration(t *testing.T) { func TestUpdateConfiguration(t *testing.T) {
originDialer := ingress.NewOriginDialer(ingress.OriginConfig{
DefaultDialer: testDefaultDialer,
TCPWriteTimeout: 1 * time.Second,
}, &testLogger)
initConfig := &Config{ initConfig := &Config{
Ingress: &ingress.Ingress{}, Ingress: &ingress.Ingress{},
OriginDialerService: originDialer,
} }
orchestrator, err := NewOrchestrator(t.Context(), initConfig, testTags, []ingress.Rule{ingress.NewManagementRule(management.New("management.argotunnel.com", false, "1.1.1.1:80", uuid.Nil, "", &testLogger, nil))}, &testLogger) orchestrator, err := NewOrchestrator(t.Context(), initConfig, testTags, []ingress.Rule{ingress.NewManagementRule(management.New("management.argotunnel.com", false, "1.1.1.1:80", uuid.Nil, "", &testLogger, nil))}, &testLogger)
require.NoError(t, err) require.NoError(t, err)
@@ -179,8 +189,13 @@ func TestUpdateConfiguration(t *testing.T) {
// Validates that a new version 0 will be applied if the configuration is loaded locally. // Validates that a new version 0 will be applied if the configuration is loaded locally.
// This will happen when a locally managed tunnel is migrated to remote configuration and receives its first configuration. // This will happen when a locally managed tunnel is migrated to remote configuration and receives its first configuration.
func TestUpdateConfiguration_FromMigration(t *testing.T) { func TestUpdateConfiguration_FromMigration(t *testing.T) {
originDialer := ingress.NewOriginDialer(ingress.OriginConfig{
DefaultDialer: testDefaultDialer,
TCPWriteTimeout: 1 * time.Second,
}, &testLogger)
initConfig := &Config{ initConfig := &Config{
Ingress: &ingress.Ingress{}, Ingress: &ingress.Ingress{},
OriginDialerService: originDialer,
} }
orchestrator, err := NewOrchestrator(t.Context(), initConfig, testTags, []ingress.Rule{}, &testLogger) orchestrator, err := NewOrchestrator(t.Context(), initConfig, testTags, []ingress.Rule{}, &testLogger)
require.NoError(t, err) require.NoError(t, err)
@@ -205,8 +220,13 @@ func TestUpdateConfiguration_FromMigration(t *testing.T) {
// Validates that the default ingress rule will be set if there is no rule provided from the remote. // Validates that the default ingress rule will be set if there is no rule provided from the remote.
func TestUpdateConfiguration_WithoutIngressRule(t *testing.T) { func TestUpdateConfiguration_WithoutIngressRule(t *testing.T) {
originDialer := ingress.NewOriginDialer(ingress.OriginConfig{
DefaultDialer: testDefaultDialer,
TCPWriteTimeout: 1 * time.Second,
}, &testLogger)
initConfig := &Config{ initConfig := &Config{
Ingress: &ingress.Ingress{}, Ingress: &ingress.Ingress{},
OriginDialerService: originDialer,
} }
orchestrator, err := NewOrchestrator(t.Context(), initConfig, testTags, []ingress.Rule{}, &testLogger) orchestrator, err := NewOrchestrator(t.Context(), initConfig, testTags, []ingress.Rule{}, &testLogger)
require.NoError(t, err) require.NoError(t, err)
@@ -244,6 +264,11 @@ func TestConcurrentUpdateAndRead(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer tcpOrigin.Close() defer tcpOrigin.Close()
originDialer := ingress.NewOriginDialer(ingress.OriginConfig{
DefaultDialer: testDefaultDialer,
TCPWriteTimeout: 1 * time.Second,
}, &testLogger)
var ( var (
configJSONV1 = []byte(fmt.Sprintf(` configJSONV1 = []byte(fmt.Sprintf(`
{ {
@@ -296,7 +321,8 @@ func TestConcurrentUpdateAndRead(t *testing.T) {
appliedV2 = make(chan struct{}) appliedV2 = make(chan struct{})
initConfig = &Config{ initConfig = &Config{
Ingress: &ingress.Ingress{}, Ingress: &ingress.Ingress{},
OriginDialerService: originDialer,
} }
) )
@@ -313,7 +339,7 @@ func TestConcurrentUpdateAndRead(t *testing.T) {
go func() { go func() {
serveTCPOrigin(t, tcpOrigin, &wg) serveTCPOrigin(t, tcpOrigin, &wg)
}() }()
for i := 0; i < concurrentRequests; i++ { for i := range concurrentRequests {
originProxy, err := orchestrator.GetOriginProxy() originProxy, err := orchestrator.GetOriginProxy()
require.NoError(t, err) require.NoError(t, err)
wg.Add(1) wg.Add(1)
@@ -323,48 +349,37 @@ func TestConcurrentUpdateAndRead(t *testing.T) {
assert.NoError(t, err, "proxyHTTP %d failed %v", i, err) assert.NoError(t, err, "proxyHTTP %d failed %v", i, err)
defer resp.Body.Close() defer resp.Body.Close()
var warpRoutingDisabled bool
// The response can be from initOrigin, http_status:204 or http_status:418 // The response can be from initOrigin, http_status:204 or http_status:418
switch resp.StatusCode { switch resp.StatusCode {
// v1 proxy, warp enabled // v1 proxy
case 200: case 200:
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, t.Name(), string(body)) assert.Equal(t, t.Name(), string(body))
warpRoutingDisabled = false // v2 proxy
// v2 proxy, warp disabled
case 204: case 204:
assert.Greater(t, i, concurrentRequests/4) assert.Greater(t, i, concurrentRequests/4)
warpRoutingDisabled = true // v3 proxy
// v3 proxy, warp enabled
case 418: case 418:
assert.Greater(t, i, concurrentRequests/2) assert.Greater(t, i, concurrentRequests/2)
warpRoutingDisabled = false
} }
// Once we have originProxy, it won't be changed by configuration updates. // Once we have originProxy, it won't be changed by configuration updates.
// We can infer the version by the ProxyHTTP response code // We can infer the version by the ProxyHTTP response code
pr, pw := io.Pipe() pr, pw := io.Pipe()
w := newRespReadWriteFlusher() w := newRespReadWriteFlusher()
// Write TCP message and make sure it's echo back. This has to be done in a go routune since ProxyTCP doesn't // Write TCP message and make sure it's echo back. This has to be done in a go routune since ProxyTCP doesn't
// return until the stream is closed. // return until the stream is closed.
if !warpRoutingDisabled { wg.Add(1)
wg.Add(1) go func() {
go func() { defer wg.Done()
defer wg.Done() defer pw.Close()
defer pw.Close() tcpEyeball(t, pw, tcpBody, w)
tcpEyeball(t, pw, tcpBody, w) }()
}()
}
err = proxyTCP(ctx, originProxy, tcpOrigin.Addr().String(), w, pr) err = proxyTCP(ctx, originProxy, tcpOrigin.Addr().String(), w, pr)
if warpRoutingDisabled { assert.NoError(t, err, "proxyTCP %d failed %v", i, err)
assert.Error(t, err, "expect proxyTCP %d to return error", i)
} else {
assert.NoError(t, err, "proxyTCP %d failed %v", i, err)
}
}(i, originProxy) }(i, originProxy)
if i == concurrentRequests/4 { if i == concurrentRequests/4 {
@@ -406,39 +421,47 @@ func TestOverrideWarpRoutingConfigWithLocalValues(t *testing.T) {
require.EqualValues(t, expectedValue, warpRouting["maxActiveFlows"]) require.EqualValues(t, expectedValue, warpRouting["maxActiveFlows"])
} }
remoteValue := uint64(100) originDialer := ingress.NewOriginDialer(ingress.OriginConfig{
remoteIngress := ingress.Ingress{} DefaultDialer: testDefaultDialer,
TCPWriteTimeout: 1 * time.Second,
}, &testLogger)
// All the possible values set for MaxActiveFlows from the various points that can provide it:
// 1. Initialized value
// 2. Local CLI flag config
// 3. Remote configuration value
initValue := uint64(0)
localValue := uint64(100)
remoteValue := uint64(500)
initConfig := &Config{
Ingress: &ingress.Ingress{},
WarpRouting: ingress.WarpRoutingConfig{
MaxActiveFlows: initValue,
},
OriginDialerService: originDialer,
ConfigurationFlags: map[string]string{
flags.MaxActiveFlows: fmt.Sprintf("%d", localValue),
},
}
// We expect the local configuration flag to be the starting value
orchestrator, err := NewOrchestrator(ctx, initConfig, testTags, []ingress.Rule{}, &testLogger)
require.NoError(t, err)
assertMaxActiveFlows(orchestrator, localValue)
// Assigning the MaxActiveFlows in the remote config should be ignored over the local config
remoteWarpConfig := ingress.WarpRoutingConfig{ remoteWarpConfig := ingress.WarpRoutingConfig{
MaxActiveFlows: remoteValue, MaxActiveFlows: remoteValue,
} }
remoteConfig := &Config{
Ingress: &remoteIngress,
WarpRouting: remoteWarpConfig,
ConfigurationFlags: map[string]string{},
}
orchestrator, err := NewOrchestrator(ctx, remoteConfig, testTags, []ingress.Rule{}, &testLogger)
require.NoError(t, err)
assertMaxActiveFlows(orchestrator, remoteValue)
// Add a local override for the maxActiveFlows
localValue := uint64(500)
remoteConfig.ConfigurationFlags[flags.MaxActiveFlows] = fmt.Sprintf("%d", localValue)
// Force a configuration refresh // Force a configuration refresh
err = orchestrator.updateIngress(remoteIngress, remoteWarpConfig) err = orchestrator.updateIngress(ingress.Ingress{}, remoteWarpConfig)
require.NoError(t, err) require.NoError(t, err)
// Check the value being used is the local one // Check the value being used is the local one
assertMaxActiveFlows(orchestrator, localValue) assertMaxActiveFlows(orchestrator, localValue)
// Remove local override for the maxActiveFlows
delete(remoteConfig.ConfigurationFlags, flags.MaxActiveFlows)
// Force a configuration refresh
err = orchestrator.updateIngress(remoteIngress, remoteWarpConfig)
require.NoError(t, err)
// Check the value being used is now the remote again
assertMaxActiveFlows(orchestrator, remoteValue)
} }
func proxyHTTP(originProxy connection.OriginProxy, hostname string) (*http.Response, error) { func proxyHTTP(originProxy connection.OriginProxy, hostname string) (*http.Response, error) {
@@ -546,6 +569,10 @@ func updateWithValidation(t *testing.T, orchestrator *Orchestrator, version int3
// TestClosePreviousProxies makes sure proxies started in the previous configuration version are shutdown // TestClosePreviousProxies makes sure proxies started in the previous configuration version are shutdown
func TestClosePreviousProxies(t *testing.T) { func TestClosePreviousProxies(t *testing.T) {
originDialer := ingress.NewOriginDialer(ingress.OriginConfig{
DefaultDialer: testDefaultDialer,
TCPWriteTimeout: 1 * time.Second,
}, &testLogger)
var ( var (
hostname = "hello.tunnel1.org" hostname = "hello.tunnel1.org"
configWithHelloWorld = []byte(fmt.Sprintf(` configWithHelloWorld = []byte(fmt.Sprintf(`
@@ -576,7 +603,8 @@ func TestClosePreviousProxies(t *testing.T) {
} }
`) `)
initConfig = &Config{ initConfig = &Config{
Ingress: &ingress.Ingress{}, Ingress: &ingress.Ingress{},
OriginDialerService: originDialer,
} }
) )
@@ -638,8 +666,13 @@ func TestPersistentConnection(t *testing.T) {
hostname = "http://ws.tunnel.org" hostname = "http://ws.tunnel.org"
) )
msg := t.Name() msg := t.Name()
originDialer := ingress.NewOriginDialer(ingress.OriginConfig{
DefaultDialer: testDefaultDialer,
TCPWriteTimeout: 1 * time.Second,
}, &testLogger)
initConfig := &Config{ initConfig := &Config{
Ingress: &ingress.Ingress{}, Ingress: &ingress.Ingress{},
OriginDialerService: originDialer,
} }
orchestrator, err := NewOrchestrator(t.Context(), initConfig, testTags, []ingress.Rule{}, &testLogger) orchestrator, err := NewOrchestrator(t.Context(), initConfig, testTags, []ingress.Rule{}, &testLogger)
require.NoError(t, err) require.NoError(t, err)
@@ -752,8 +785,9 @@ func TestSerializeLocalConfig(t *testing.T) {
ConfigurationFlags: map[string]string{"a": "b"}, ConfigurationFlags: map[string]string{"a": "b"},
} }
result, _ := json.Marshal(c) result, err := json.Marshal(c)
fmt.Println(string(result)) require.NoError(t, err)
require.JSONEq(t, `{"__configuration_flags":{"a":"b"},"ingress":[],"warp-routing":{"connectTimeout":0,"tcpKeepAlive":0}}`, string(result))
} }
func wsEcho(w http.ResponseWriter, r *http.Request) { func wsEcho(w http.ResponseWriter, r *http.Request) {

View File

@@ -5,11 +5,11 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"net/netip"
"strconv" "strconv"
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
pkgerrors "github.com/pkg/errors"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
@@ -35,7 +35,7 @@ const (
// Proxy represents a means to Proxy between cloudflared and the origin services. // Proxy represents a means to Proxy between cloudflared and the origin services.
type Proxy struct { type Proxy struct {
ingressRules ingress.Ingress ingressRules ingress.Ingress
warpRouting *ingress.WarpRoutingService originDialer ingress.OriginTCPDialer
tags []pogs.Tag tags []pogs.Tag
flowLimiter cfdflow.Limiter flowLimiter cfdflow.Limiter
log *zerolog.Logger log *zerolog.Logger
@@ -44,21 +44,19 @@ type Proxy struct {
// NewOriginProxy returns a new instance of the Proxy struct. // NewOriginProxy returns a new instance of the Proxy struct.
func NewOriginProxy( func NewOriginProxy(
ingressRules ingress.Ingress, ingressRules ingress.Ingress,
warpRouting ingress.WarpRoutingConfig, originDialer ingress.OriginDialer,
tags []pogs.Tag, tags []pogs.Tag,
flowLimiter cfdflow.Limiter, flowLimiter cfdflow.Limiter,
writeTimeout time.Duration,
log *zerolog.Logger, log *zerolog.Logger,
) *Proxy { ) *Proxy {
proxy := &Proxy{ proxy := &Proxy{
ingressRules: ingressRules, ingressRules: ingressRules,
originDialer: originDialer,
tags: tags, tags: tags,
flowLimiter: flowLimiter, flowLimiter: flowLimiter,
log: log, log: log,
} }
proxy.warpRouting = ingress.NewWarpRoutingService(warpRouting, writeTimeout)
return proxy return proxy
} }
@@ -146,24 +144,18 @@ func (p *Proxy) ProxyHTTP(
// ProxyTCP proxies to a TCP connection between the origin service and cloudflared. // ProxyTCP proxies to a TCP connection between the origin service and cloudflared.
func (p *Proxy) ProxyTCP( func (p *Proxy) ProxyTCP(
ctx context.Context, ctx context.Context,
rwa connection.ReadWriteAcker, conn connection.ReadWriteAcker,
req *connection.TCPRequest, req *connection.TCPRequest,
) error { ) error {
incrementTCPRequests() incrementTCPRequests()
defer decrementTCPConcurrentRequests() defer decrementTCPConcurrentRequests()
if p.warpRouting == nil {
err := errors.New(`cloudflared received a request from WARP client, but your configuration has disabled ingress from WARP clients. To enable this, set "warp-routing:\n\t enabled: true" in your config.yaml`)
p.log.Error().Msg(err.Error())
return err
}
logger := newTCPLogger(p.log, req) logger := newTCPLogger(p.log, req)
// Try to start a new flow // Try to start a new flow
if err := p.flowLimiter.Acquire(management.TCP.String()); err != nil { if err := p.flowLimiter.Acquire(management.TCP.String()); err != nil {
logger.Warn().Msg("Too many concurrent flows being handled, rejecting tcp proxy") logger.Warn().Msg("Too many concurrent flows being handled, rejecting tcp proxy")
return pkgerrors.Wrap(err, "failed to start tcp flow due to rate limiting") return errors.Wrap(err, "failed to start tcp flow due to rate limiting")
} }
defer p.flowLimiter.Release() defer p.flowLimiter.Release()
@@ -173,7 +165,14 @@ func (p *Proxy) ProxyTCP(
tracedCtx := tracing.NewTracedContext(serveCtx, req.CfTraceID, &logger) tracedCtx := tracing.NewTracedContext(serveCtx, req.CfTraceID, &logger)
logger.Debug().Msg("tcp proxy stream started") logger.Debug().Msg("tcp proxy stream started")
if err := p.proxyStream(tracedCtx, rwa, req.Dest, p.warpRouting.Proxy, &logger); err != nil { // Parse the destination into a netip.AddrPort
dest, err := netip.ParseAddrPort(req.Dest)
if err != nil {
logRequestError(&logger, err)
return err
}
if err := p.proxyTCPStream(tracedCtx, conn, dest, p.originDialer, &logger); err != nil {
logRequestError(&logger, err) logRequestError(&logger, err)
return err return err
} }
@@ -279,14 +278,14 @@ func (p *Proxy) proxyStream(
tr *tracing.TracedContext, tr *tracing.TracedContext,
rwa connection.ReadWriteAcker, rwa connection.ReadWriteAcker,
dest string, dest string,
connectionProxy ingress.StreamBasedOriginProxy, originDialer ingress.StreamBasedOriginProxy,
logger *zerolog.Logger, logger *zerolog.Logger,
) error { ) error {
ctx := tr.Context ctx := tr.Context
_, connectSpan := tr.Tracer().Start(ctx, "stream-connect") _, connectSpan := tr.Tracer().Start(ctx, "stream-connect")
start := time.Now() start := time.Now()
originConn, err := connectionProxy.EstablishConnection(ctx, dest, logger) originConn, err := originDialer.EstablishConnection(ctx, dest, logger)
if err != nil { if err != nil {
connectStreamErrors.Inc() connectStreamErrors.Inc()
tracing.EndWithErrorStatus(connectSpan, err) tracing.EndWithErrorStatus(connectSpan, err)
@@ -310,6 +309,45 @@ func (p *Proxy) proxyStream(
return nil return nil
} }
// proxyTCPStream proxies private network type TCP connections as a stream towards an available origin.
//
// This is different than proxyStream because it's not leveraged ingress rule services and uses the
// originDialer from OriginDialerService.
func (p *Proxy) proxyTCPStream(
tr *tracing.TracedContext,
tunnelConn connection.ReadWriteAcker,
dest netip.AddrPort,
originDialer ingress.OriginTCPDialer,
logger *zerolog.Logger,
) error {
ctx := tr.Context
_, connectSpan := tr.Tracer().Start(ctx, "stream-connect")
start := time.Now()
originConn, err := originDialer.DialTCP(ctx, dest)
if err != nil {
connectStreamErrors.Inc()
tracing.EndWithErrorStatus(connectSpan, err)
return err
}
connectSpan.End()
defer originConn.Close()
logger.Debug().Msg("origin connection established")
encodedSpans := tr.GetSpans()
if err := tunnelConn.AckConnection(encodedSpans); err != nil {
connectStreamErrors.Inc()
return err
}
connectLatency.Observe(float64(time.Since(start).Milliseconds()))
logger.Debug().Msg("proxy stream acknowledged")
stream.Pipe(tunnelConn, originConn, logger)
return nil
}
func (p *Proxy) proxyLocalRequest(proxy ingress.HTTPLocalProxy, w connection.ResponseWriter, req *http.Request, isWebsocket bool) { func (p *Proxy) proxyLocalRequest(proxy ingress.HTTPLocalProxy, w connection.ResponseWriter, req *http.Request, isWebsocket bool) {
if isWebsocket { if isWebsocket {
// These headers are added since they are stripped off during an eyeball request to origintunneld, but they // These headers are added since they are stripped off during an eyeball request to origintunneld, but they

View File

@@ -33,17 +33,17 @@ import (
"github.com/cloudflare/cloudflared/connection" "github.com/cloudflare/cloudflared/connection"
"github.com/cloudflare/cloudflared/hello" "github.com/cloudflare/cloudflared/hello"
"github.com/cloudflare/cloudflared/ingress" "github.com/cloudflare/cloudflared/ingress"
"github.com/cloudflare/cloudflared/logger"
"github.com/cloudflare/cloudflared/tracing" "github.com/cloudflare/cloudflared/tracing"
"github.com/cloudflare/cloudflared/tunnelrpc/pogs" "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
) )
var ( var (
testTags = []pogs.Tag{{Name: "Name", Value: "value"}} testTags = []pogs.Tag{{Name: "Name", Value: "value"}}
noWarpRouting = ingress.WarpRoutingConfig{} testDefaultDialer = ingress.NewDialer(ingress.WarpRoutingConfig{
testWarpRouting = ingress.WarpRoutingConfig{ ConnectTimeout: config.CustomDuration{Duration: 1 * time.Second},
ConnectTimeout: config.CustomDuration{Duration: time.Second}, TCPKeepAlive: config.CustomDuration{Duration: 15 * time.Second},
} MaxActiveFlows: 0,
})
) )
type mockHTTPRespWriter struct { type mockHTTPRespWriter struct {
@@ -162,7 +162,12 @@ func TestProxySingleOrigin(t *testing.T) {
require.NoError(t, ingressRule.StartOrigins(&log, ctx.Done())) require.NoError(t, ingressRule.StartOrigins(&log, ctx.Done()))
proxy := NewOriginProxy(ingressRule, noWarpRouting, testTags, cfdflow.NewLimiter(0), time.Duration(0), &log) originDialer := ingress.NewOriginDialer(ingress.OriginConfig{
DefaultDialer: testDefaultDialer,
TCPWriteTimeout: 1 * time.Second,
}, &log)
proxy := NewOriginProxy(ingressRule, originDialer, testTags, cfdflow.NewLimiter(0), &log)
t.Run("testProxyHTTP", testProxyHTTP(proxy)) t.Run("testProxyHTTP", testProxyHTTP(proxy))
t.Run("testProxyWebsocket", testProxyWebsocket(proxy)) t.Run("testProxyWebsocket", testProxyWebsocket(proxy))
t.Run("testProxySSE", testProxySSE(proxy)) t.Run("testProxySSE", testProxySSE(proxy))
@@ -357,7 +362,7 @@ type MultipleIngressTest struct {
} }
func runIngressTestScenarios(t *testing.T, unvalidatedIngress []config.UnvalidatedIngressRule, tests []MultipleIngressTest) { func runIngressTestScenarios(t *testing.T, unvalidatedIngress []config.UnvalidatedIngressRule, tests []MultipleIngressTest) {
ingress, err := ingress.ParseIngress(&config.Configuration{ ingressRule, err := ingress.ParseIngress(&config.Configuration{
TunnelID: t.Name(), TunnelID: t.Name(),
Ingress: unvalidatedIngress, Ingress: unvalidatedIngress,
}) })
@@ -366,9 +371,14 @@ func runIngressTestScenarios(t *testing.T, unvalidatedIngress []config.Unvalidat
log := zerolog.Nop() log := zerolog.Nop()
ctx, cancel := context.WithCancel(t.Context()) ctx, cancel := context.WithCancel(t.Context())
require.NoError(t, ingress.StartOrigins(&log, ctx.Done())) require.NoError(t, ingressRule.StartOrigins(&log, ctx.Done()))
proxy := NewOriginProxy(ingress, noWarpRouting, testTags, cfdflow.NewLimiter(0), time.Duration(0), &log) originDialer := ingress.NewOriginDialer(ingress.OriginConfig{
DefaultDialer: testDefaultDialer,
TCPWriteTimeout: 1 * time.Second,
}, &log)
proxy := NewOriginProxy(ingressRule, originDialer, testTags, cfdflow.NewLimiter(0), &log)
for _, test := range tests { for _, test := range tests {
responseWriter := newMockHTTPRespWriter() responseWriter := newMockHTTPRespWriter()
@@ -416,7 +426,12 @@ func TestProxyError(t *testing.T) {
log := zerolog.Nop() log := zerolog.Nop()
proxy := NewOriginProxy(ing, noWarpRouting, testTags, cfdflow.NewLimiter(0), time.Duration(0), &log) originDialer := ingress.NewOriginDialer(ingress.OriginConfig{
DefaultDialer: testDefaultDialer,
TCPWriteTimeout: 1 * time.Second,
}, &log)
proxy := NewOriginProxy(ing, originDialer, testTags, cfdflow.NewLimiter(0), &log)
responseWriter := newMockHTTPRespWriter() responseWriter := newMockHTTPRespWriter()
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1", nil) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1", nil)
@@ -467,7 +482,7 @@ func (r *replayer) Bytes() []byte {
// WS - TCP: When a tcp based ingress is configured on the origin and the // WS - TCP: When a tcp based ingress is configured on the origin and the
// eyeball sends tcp packets wrapped in websockets. (E.g: cloudflared access). // eyeball sends tcp packets wrapped in websockets. (E.g: cloudflared access).
func TestConnections(t *testing.T) { func TestConnections(t *testing.T) {
logger := logger.Create(nil) log := zerolog.Nop()
replayer := &replayer{rw: bytes.NewBuffer([]byte{})} replayer := &replayer{rw: bytes.NewBuffer([]byte{})}
type args struct { type args struct {
ingressServiceScheme string ingressServiceScheme string
@@ -475,9 +490,6 @@ func TestConnections(t *testing.T) {
eyeballResponseWriter connection.ResponseWriter eyeballResponseWriter connection.ResponseWriter
eyeballRequestBody io.ReadCloser eyeballRequestBody io.ReadCloser
// Can be set to nil to show warp routing is not enabled.
warpRoutingService *ingress.WarpRoutingService
// eyeball connection type. // eyeball connection type.
connectionType connection.Type connectionType connection.Type
@@ -488,6 +500,11 @@ func TestConnections(t *testing.T) {
flowLimiterResponse error flowLimiterResponse error
} }
originDialer := ingress.NewOriginDialer(ingress.OriginConfig{
DefaultDialer: testDefaultDialer,
TCPWriteTimeout: 0,
}, &log)
type want struct { type want struct {
message []byte message []byte
headers http.Header headers http.Header
@@ -530,7 +547,6 @@ func TestConnections(t *testing.T) {
originService: runEchoTCPService, originService: runEchoTCPService,
eyeballResponseWriter: newTCPRespWriter(replayer), eyeballResponseWriter: newTCPRespWriter(replayer),
eyeballRequestBody: newTCPRequestBody([]byte("test2")), eyeballRequestBody: newTCPRequestBody([]byte("test2")),
warpRoutingService: ingress.NewWarpRoutingService(testWarpRouting, time.Duration(0)),
connectionType: connection.TypeTCP, connectionType: connection.TypeTCP,
requestHeaders: map[string][]string{ requestHeaders: map[string][]string{
"Cf-Cloudflared-Proxy-Src": {"non-blank-value"}, "Cf-Cloudflared-Proxy-Src": {"non-blank-value"},
@@ -548,7 +564,6 @@ func TestConnections(t *testing.T) {
originService: runEchoWSService, originService: runEchoWSService,
// eyeballResponseWriter gets set after roundtrip dial. // eyeballResponseWriter gets set after roundtrip dial.
eyeballRequestBody: newPipedWSRequestBody([]byte("test3")), eyeballRequestBody: newPipedWSRequestBody([]byte("test3")),
warpRoutingService: ingress.NewWarpRoutingService(testWarpRouting, time.Duration(0)),
requestHeaders: map[string][]string{ requestHeaders: map[string][]string{
"Cf-Cloudflared-Proxy-Src": {"non-blank-value"}, "Cf-Cloudflared-Proxy-Src": {"non-blank-value"},
}, },
@@ -601,23 +616,6 @@ func TestConnections(t *testing.T) {
headers: map[string][]string{}, headers: map[string][]string{},
}, },
}, },
{
name: "tcp-tcp proxy without warpRoutingService enabled",
args: args{
ingressServiceScheme: "tcp://",
originService: runEchoTCPService,
eyeballResponseWriter: newTCPRespWriter(replayer),
eyeballRequestBody: newTCPRequestBody([]byte("test2")),
connectionType: connection.TypeTCP,
requestHeaders: map[string][]string{
"Cf-Cloudflared-Proxy-Src": {"non-blank-value"},
},
},
want: want{
message: []byte{},
err: true,
},
},
{ {
name: "ws-ws proxy when origin is different", name: "ws-ws proxy when origin is different",
args: args{ args: args{
@@ -670,7 +668,6 @@ func TestConnections(t *testing.T) {
originService: runEchoTCPService, originService: runEchoTCPService,
eyeballResponseWriter: newTCPRespWriter(replayer), eyeballResponseWriter: newTCPRespWriter(replayer),
eyeballRequestBody: newTCPRequestBody([]byte("rate-limited")), eyeballRequestBody: newTCPRequestBody([]byte("rate-limited")),
warpRoutingService: ingress.NewWarpRoutingService(testWarpRouting, time.Duration(0)),
connectionType: connection.TypeTCP, connectionType: connection.TypeTCP,
requestHeaders: map[string][]string{ requestHeaders: map[string][]string{
"Cf-Cloudflared-Proxy-Src": {"non-blank-value"}, "Cf-Cloudflared-Proxy-Src": {"non-blank-value"},
@@ -693,7 +690,7 @@ func TestConnections(t *testing.T) {
test.args.originService(t, ln) test.args.originService(t, ln)
ingressRule := createSingleIngressConfig(t, test.args.ingressServiceScheme+ln.Addr().String()) ingressRule := createSingleIngressConfig(t, test.args.ingressServiceScheme+ln.Addr().String())
_ = ingressRule.StartOrigins(logger, ctx.Done()) _ = ingressRule.StartOrigins(&log, ctx.Done())
// Mock flow limiter // Mock flow limiter
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
@@ -702,8 +699,7 @@ func TestConnections(t *testing.T) {
flowLimiter.EXPECT().Acquire("tcp").AnyTimes().Return(test.args.flowLimiterResponse) flowLimiter.EXPECT().Acquire("tcp").AnyTimes().Return(test.args.flowLimiterResponse)
flowLimiter.EXPECT().Release().AnyTimes() flowLimiter.EXPECT().Release().AnyTimes()
proxy := NewOriginProxy(ingressRule, testWarpRouting, testTags, flowLimiter, time.Duration(0), logger) proxy := NewOriginProxy(ingressRule, originDialer, testTags, flowLimiter, &log)
proxy.warpRouting = test.args.warpRoutingService
dest := ln.Addr().String() dest := ln.Addr().String()
req, err := http.NewRequest( req, err := http.NewRequest(

View File

@@ -40,13 +40,13 @@ type SessionManager interface {
type sessionManager struct { type sessionManager struct {
sessions map[RequestID]Session sessions map[RequestID]Session
mutex sync.RWMutex mutex sync.RWMutex
originDialer ingress.UDPOriginProxy originDialer ingress.OriginUDPDialer
limiter cfdflow.Limiter limiter cfdflow.Limiter
metrics Metrics metrics Metrics
log *zerolog.Logger log *zerolog.Logger
} }
func NewSessionManager(metrics Metrics, log *zerolog.Logger, originDialer ingress.UDPOriginProxy, limiter cfdflow.Limiter) SessionManager { func NewSessionManager(metrics Metrics, log *zerolog.Logger, originDialer ingress.OriginUDPDialer, limiter cfdflow.Limiter) SessionManager {
return &sessionManager{ return &sessionManager{
sessions: make(map[RequestID]Session), sessions: make(map[RequestID]Session),
originDialer: originDialer, originDialer: originDialer,

View File

@@ -11,6 +11,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uber.org/mock/gomock" "go.uber.org/mock/gomock"
"github.com/cloudflare/cloudflared/config"
"github.com/cloudflare/cloudflared/mocks" "github.com/cloudflare/cloudflared/mocks"
cfdflow "github.com/cloudflare/cloudflared/flow" cfdflow "github.com/cloudflare/cloudflared/flow"
@@ -18,9 +19,21 @@ import (
v3 "github.com/cloudflare/cloudflared/quic/v3" v3 "github.com/cloudflare/cloudflared/quic/v3"
) )
var (
testDefaultDialer = ingress.NewDialer(ingress.WarpRoutingConfig{
ConnectTimeout: config.CustomDuration{Duration: 1 * time.Second},
TCPKeepAlive: config.CustomDuration{Duration: 15 * time.Second},
MaxActiveFlows: 0,
})
)
func TestRegisterSession(t *testing.T) { func TestRegisterSession(t *testing.T) {
log := zerolog.Nop() log := zerolog.Nop()
manager := v3.NewSessionManager(&noopMetrics{}, &log, ingress.DefaultUDPDialer, cfdflow.NewLimiter(0)) originDialerService := ingress.NewOriginDialer(ingress.OriginConfig{
DefaultDialer: testDefaultDialer,
TCPWriteTimeout: 0,
}, &log)
manager := v3.NewSessionManager(&noopMetrics{}, &log, originDialerService, cfdflow.NewLimiter(0))
request := v3.UDPSessionRegistrationDatagram{ request := v3.UDPSessionRegistrationDatagram{
RequestID: testRequestID, RequestID: testRequestID,
@@ -76,7 +89,11 @@ func TestRegisterSession(t *testing.T) {
func TestGetSession_Empty(t *testing.T) { func TestGetSession_Empty(t *testing.T) {
log := zerolog.Nop() log := zerolog.Nop()
manager := v3.NewSessionManager(&noopMetrics{}, &log, ingress.DefaultUDPDialer, cfdflow.NewLimiter(0)) originDialerService := ingress.NewOriginDialer(ingress.OriginConfig{
DefaultDialer: testDefaultDialer,
TCPWriteTimeout: 0,
}, &log)
manager := v3.NewSessionManager(&noopMetrics{}, &log, originDialerService, cfdflow.NewLimiter(0))
_, err := manager.GetSession(testRequestID) _, err := manager.GetSession(testRequestID)
if !errors.Is(err, v3.ErrSessionNotFound) { if !errors.Is(err, v3.ErrSessionNotFound) {
@@ -86,6 +103,10 @@ func TestGetSession_Empty(t *testing.T) {
func TestRegisterSessionRateLimit(t *testing.T) { func TestRegisterSessionRateLimit(t *testing.T) {
log := zerolog.Nop() log := zerolog.Nop()
originDialerService := ingress.NewOriginDialer(ingress.OriginConfig{
DefaultDialer: testDefaultDialer,
TCPWriteTimeout: 0,
}, &log)
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
flowLimiterMock := mocks.NewMockLimiter(ctrl) flowLimiterMock := mocks.NewMockLimiter(ctrl)
@@ -93,7 +114,7 @@ func TestRegisterSessionRateLimit(t *testing.T) {
flowLimiterMock.EXPECT().Acquire("udp").Return(cfdflow.ErrTooManyActiveFlows) flowLimiterMock.EXPECT().Acquire("udp").Return(cfdflow.ErrTooManyActiveFlows)
flowLimiterMock.EXPECT().Release().Times(0) flowLimiterMock.EXPECT().Release().Times(0)
manager := v3.NewSessionManager(&noopMetrics{}, &log, ingress.DefaultUDPDialer, flowLimiterMock) manager := v3.NewSessionManager(&noopMetrics{}, &log, originDialerService, flowLimiterMock)
request := v3.UDPSessionRegistrationDatagram{ request := v3.UDPSessionRegistrationDatagram{
RequestID: testRequestID, RequestID: testRequestID,

View File

@@ -88,7 +88,11 @@ func (m *mockEyeball) SendICMPTTLExceed(icmp *packet.ICMP, rawPacket packet.RawP
func TestDatagramConn_New(t *testing.T) { func TestDatagramConn_New(t *testing.T) {
log := zerolog.Nop() log := zerolog.Nop()
conn := v3.NewDatagramConn(newMockQuicConn(), v3.NewSessionManager(&noopMetrics{}, &log, ingress.DefaultUDPDialer, cfdflow.NewLimiter(0)), &noopICMPRouter{}, 0, &noopMetrics{}, &log) originDialerService := ingress.NewOriginDialer(ingress.OriginConfig{
DefaultDialer: testDefaultDialer,
TCPWriteTimeout: 0,
}, &log)
conn := v3.NewDatagramConn(newMockQuicConn(), v3.NewSessionManager(&noopMetrics{}, &log, originDialerService, cfdflow.NewLimiter(0)), &noopICMPRouter{}, 0, &noopMetrics{}, &log)
if conn == nil { if conn == nil {
t.Fatal("expected valid connection") t.Fatal("expected valid connection")
} }
@@ -96,8 +100,12 @@ func TestDatagramConn_New(t *testing.T) {
func TestDatagramConn_SendUDPSessionDatagram(t *testing.T) { func TestDatagramConn_SendUDPSessionDatagram(t *testing.T) {
log := zerolog.Nop() log := zerolog.Nop()
originDialerService := ingress.NewOriginDialer(ingress.OriginConfig{
DefaultDialer: testDefaultDialer,
TCPWriteTimeout: 0,
}, &log)
quic := newMockQuicConn() quic := newMockQuicConn()
conn := v3.NewDatagramConn(quic, v3.NewSessionManager(&noopMetrics{}, &log, ingress.DefaultUDPDialer, cfdflow.NewLimiter(0)), &noopICMPRouter{}, 0, &noopMetrics{}, &log) conn := v3.NewDatagramConn(quic, v3.NewSessionManager(&noopMetrics{}, &log, originDialerService, cfdflow.NewLimiter(0)), &noopICMPRouter{}, 0, &noopMetrics{}, &log)
payload := []byte{0xef, 0xef} payload := []byte{0xef, 0xef}
err := conn.SendUDPSessionDatagram(payload) err := conn.SendUDPSessionDatagram(payload)
@@ -111,8 +119,12 @@ func TestDatagramConn_SendUDPSessionDatagram(t *testing.T) {
func TestDatagramConn_SendUDPSessionResponse(t *testing.T) { func TestDatagramConn_SendUDPSessionResponse(t *testing.T) {
log := zerolog.Nop() log := zerolog.Nop()
originDialerService := ingress.NewOriginDialer(ingress.OriginConfig{
DefaultDialer: testDefaultDialer,
TCPWriteTimeout: 0,
}, &log)
quic := newMockQuicConn() quic := newMockQuicConn()
conn := v3.NewDatagramConn(quic, v3.NewSessionManager(&noopMetrics{}, &log, ingress.DefaultUDPDialer, cfdflow.NewLimiter(0)), &noopICMPRouter{}, 0, &noopMetrics{}, &log) conn := v3.NewDatagramConn(quic, v3.NewSessionManager(&noopMetrics{}, &log, originDialerService, cfdflow.NewLimiter(0)), &noopICMPRouter{}, 0, &noopMetrics{}, &log)
err := conn.SendUDPSessionResponse(testRequestID, v3.ResponseDestinationUnreachable) err := conn.SendUDPSessionResponse(testRequestID, v3.ResponseDestinationUnreachable)
require.NoError(t, err) require.NoError(t, err)
@@ -133,8 +145,12 @@ func TestDatagramConn_SendUDPSessionResponse(t *testing.T) {
func TestDatagramConnServe_ApplicationClosed(t *testing.T) { func TestDatagramConnServe_ApplicationClosed(t *testing.T) {
log := zerolog.Nop() log := zerolog.Nop()
originDialerService := ingress.NewOriginDialer(ingress.OriginConfig{
DefaultDialer: testDefaultDialer,
TCPWriteTimeout: 0,
}, &log)
quic := newMockQuicConn() quic := newMockQuicConn()
conn := v3.NewDatagramConn(quic, v3.NewSessionManager(&noopMetrics{}, &log, ingress.DefaultUDPDialer, cfdflow.NewLimiter(0)), &noopICMPRouter{}, 0, &noopMetrics{}, &log) conn := v3.NewDatagramConn(quic, v3.NewSessionManager(&noopMetrics{}, &log, originDialerService, cfdflow.NewLimiter(0)), &noopICMPRouter{}, 0, &noopMetrics{}, &log)
ctx, cancel := context.WithTimeout(t.Context(), 1*time.Second) ctx, cancel := context.WithTimeout(t.Context(), 1*time.Second)
defer cancel() defer cancel()
@@ -146,11 +162,15 @@ func TestDatagramConnServe_ApplicationClosed(t *testing.T) {
func TestDatagramConnServe_ConnectionClosed(t *testing.T) { func TestDatagramConnServe_ConnectionClosed(t *testing.T) {
log := zerolog.Nop() log := zerolog.Nop()
originDialerService := ingress.NewOriginDialer(ingress.OriginConfig{
DefaultDialer: testDefaultDialer,
TCPWriteTimeout: 0,
}, &log)
quic := newMockQuicConn() quic := newMockQuicConn()
ctx, cancel := context.WithTimeout(t.Context(), 1*time.Second) ctx, cancel := context.WithTimeout(t.Context(), 1*time.Second)
defer cancel() defer cancel()
quic.ctx = ctx quic.ctx = ctx
conn := v3.NewDatagramConn(quic, v3.NewSessionManager(&noopMetrics{}, &log, ingress.DefaultUDPDialer, cfdflow.NewLimiter(0)), &noopICMPRouter{}, 0, &noopMetrics{}, &log) conn := v3.NewDatagramConn(quic, v3.NewSessionManager(&noopMetrics{}, &log, originDialerService, cfdflow.NewLimiter(0)), &noopICMPRouter{}, 0, &noopMetrics{}, &log)
err := conn.Serve(t.Context()) err := conn.Serve(t.Context())
if !errors.Is(err, context.DeadlineExceeded) { if !errors.Is(err, context.DeadlineExceeded) {
@@ -160,8 +180,12 @@ func TestDatagramConnServe_ConnectionClosed(t *testing.T) {
func TestDatagramConnServe_ReceiveDatagramError(t *testing.T) { func TestDatagramConnServe_ReceiveDatagramError(t *testing.T) {
log := zerolog.Nop() log := zerolog.Nop()
originDialerService := ingress.NewOriginDialer(ingress.OriginConfig{
DefaultDialer: testDefaultDialer,
TCPWriteTimeout: 0,
}, &log)
quic := &mockQuicConnReadError{err: net.ErrClosed} quic := &mockQuicConnReadError{err: net.ErrClosed}
conn := v3.NewDatagramConn(quic, v3.NewSessionManager(&noopMetrics{}, &log, ingress.DefaultUDPDialer, cfdflow.NewLimiter(0)), &noopICMPRouter{}, 0, &noopMetrics{}, &log) conn := v3.NewDatagramConn(quic, v3.NewSessionManager(&noopMetrics{}, &log, originDialerService, cfdflow.NewLimiter(0)), &noopICMPRouter{}, 0, &noopMetrics{}, &log)
err := conn.Serve(t.Context()) err := conn.Serve(t.Context())
if !errors.Is(err, net.ErrClosed) { if !errors.Is(err, net.ErrClosed) {

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"errors" "errors"
"net" "net"
"net/netip"
"strings" "strings"
"time" "time"
@@ -14,8 +13,6 @@ import (
"github.com/cloudflare/cloudflared/connection" "github.com/cloudflare/cloudflared/connection"
"github.com/cloudflare/cloudflared/edgediscovery" "github.com/cloudflare/cloudflared/edgediscovery"
"github.com/cloudflare/cloudflared/ingress"
"github.com/cloudflare/cloudflared/ingress/origins"
"github.com/cloudflare/cloudflared/orchestration" "github.com/cloudflare/cloudflared/orchestration"
v3 "github.com/cloudflare/cloudflared/quic/v3" v3 "github.com/cloudflare/cloudflared/quic/v3"
"github.com/cloudflare/cloudflared/retry" "github.com/cloudflare/cloudflared/retry"
@@ -81,16 +78,11 @@ func NewSupervisor(config *TunnelConfig, orchestrator *orchestration.Orchestrato
datagramMetrics := v3.NewMetrics(prometheus.DefaultRegisterer) datagramMetrics := v3.NewMetrics(prometheus.DefaultRegisterer)
// Setup the reserved virtual origins sessionManager := v3.NewSessionManager(datagramMetrics, config.Log, config.OriginDialerService, orchestrator.GetFlowLimiter())
reservedServices := map[netip.AddrPort]ingress.UDPOriginProxy{}
reservedServices[origins.VirtualDNSServiceAddr] = config.OriginDNSService
ingressUDPService := ingress.NewUDPOriginService(reservedServices, config.Log)
sessionManager := v3.NewSessionManager(datagramMetrics, config.Log, ingressUDPService, orchestrator.GetFlowLimiter())
edgeTunnelServer := EdgeTunnelServer{ edgeTunnelServer := EdgeTunnelServer{
config: config, config: config,
orchestrator: orchestrator, orchestrator: orchestrator,
ingressUDPProxy: ingressUDPService,
sessionManager: sessionManager, sessionManager: sessionManager,
datagramMetrics: datagramMetrics, datagramMetrics: datagramMetrics,
edgeAddrs: edgeIPs, edgeAddrs: edgeIPs,

View File

@@ -61,11 +61,12 @@ type TunnelConfig struct {
NeedPQ bool NeedPQ bool
NamedTunnel *connection.TunnelProperties NamedTunnel *connection.TunnelProperties
ProtocolSelector connection.ProtocolSelector ProtocolSelector connection.ProtocolSelector
EdgeTLSConfigs map[connection.Protocol]*tls.Config EdgeTLSConfigs map[connection.Protocol]*tls.Config
ICMPRouterServer ingress.ICMPRouterServer ICMPRouterServer ingress.ICMPRouterServer
OriginDNSService *origins.DNSResolverService OriginDNSService *origins.DNSResolverService
OriginDialerService *ingress.OriginDialerService
RPCTimeout time.Duration RPCTimeout time.Duration
WriteStreamTimeout time.Duration WriteStreamTimeout time.Duration
@@ -168,7 +169,6 @@ func (f *ipAddrFallback) ShouldGetNewAddress(connIndex uint8, err error) (needsN
type EdgeTunnelServer struct { type EdgeTunnelServer struct {
config *TunnelConfig config *TunnelConfig
orchestrator *orchestration.Orchestrator orchestrator *orchestration.Orchestrator
ingressUDPProxy ingress.UDPOriginProxy
sessionManager v3.SessionManager sessionManager v3.SessionManager
datagramMetrics v3.Metrics datagramMetrics v3.Metrics
edgeAddrHandler EdgeAddrHandler edgeAddrHandler EdgeAddrHandler
@@ -616,7 +616,7 @@ func (e *EdgeTunnelServer) serveQUIC(
datagramSessionManager = connection.NewDatagramV2Connection( datagramSessionManager = connection.NewDatagramV2Connection(
ctx, ctx,
conn, conn,
e.ingressUDPProxy, e.config.OriginDialerService,
e.config.ICMPRouterServer, e.config.ICMPRouterServer,
connIndex, connIndex,
e.config.RPCTimeout, e.config.RPCTimeout,