Skip to content

Commit 70ed7ff

Browse files
committed
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
1 parent 9ca8b41 commit 70ed7ff

File tree

19 files changed

+503
-254
lines changed

19 files changed

+503
-254
lines changed

cmd/cloudflared/tunnel/configuration.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,15 @@ func prepareTunnelConfig(
220220
resolvedRegion = endpoint
221221
}
222222

223-
dnsService := origins.NewDNSResolver(log)
223+
warpRoutingConfig := ingress.NewWarpRoutingConfig(&cfg.WarpRouting)
224+
225+
// Setup origin dialer service and virtual services
226+
originDialerService := ingress.NewOriginDialer(ingress.OriginConfig{
227+
DefaultDialer: ingress.NewDialer(warpRoutingConfig),
228+
TCPWriteTimeout: c.Duration(flags.WriteStreamTimeout),
229+
}, log)
230+
dnsService := origins.NewDNSResolverService(origins.NewDNSDialer(), log)
231+
originDialerService.AddReservedService(dnsService, []netip.AddrPort{origins.VirtualDNSServiceAddr})
224232

225233
tunnelConfig := &supervisor.TunnelConfig{
226234
ClientConfig: clientConfig,
@@ -250,6 +258,7 @@ func prepareTunnelConfig(
250258
QUICConnectionLevelFlowControlLimit: c.Uint64(flags.QuicConnLevelFlowControlLimit),
251259
QUICStreamLevelFlowControlLimit: c.Uint64(flags.QuicStreamLevelFlowControlLimit),
252260
OriginDNSService: dnsService,
261+
OriginDialerService: originDialerService,
253262
}
254263
icmpRouter, err := newICMPRouter(c, log)
255264
if err != nil {
@@ -258,10 +267,10 @@ func prepareTunnelConfig(
258267
tunnelConfig.ICMPRouterServer = icmpRouter
259268
}
260269
orchestratorConfig := &orchestration.Config{
261-
Ingress: &ingressRules,
262-
WarpRouting: ingress.NewWarpRoutingConfig(&cfg.WarpRouting),
263-
ConfigurationFlags: parseConfigFlags(c),
264-
WriteTimeout: tunnelConfig.WriteStreamTimeout,
270+
Ingress: &ingressRules,
271+
WarpRouting: warpRoutingConfig,
272+
OriginDialerService: originDialerService,
273+
ConfigurationFlags: parseConfigFlags(c),
265274
}
266275
return tunnelConfig, orchestratorConfig, nil
267276
}

connection/quic_connection_test.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"golang.org/x/net/nettest"
3131

3232
"github.com/cloudflare/cloudflared/client"
33+
"github.com/cloudflare/cloudflared/config"
3334
cfdflow "github.com/cloudflare/cloudflared/flow"
3435

3536
"github.com/cloudflare/cloudflared/datagramsession"
@@ -823,14 +824,23 @@ func testTunnelConnection(t *testing.T, serverAddr netip.AddrPort, index uint8)
823824
sessionManager := datagramsession.NewManager(&log, datagramMuxer.SendToSession, sessionDemuxChan)
824825
var connIndex uint8 = 0
825826
packetRouter := ingress.NewPacketRouter(nil, datagramMuxer, connIndex, &log)
827+
testDefaultDialer := ingress.NewDialer(ingress.WarpRoutingConfig{
828+
ConnectTimeout: config.CustomDuration{Duration: 1 * time.Second},
829+
TCPKeepAlive: config.CustomDuration{Duration: 15 * time.Second},
830+
MaxActiveFlows: 0,
831+
})
832+
originDialer := ingress.NewOriginDialer(ingress.OriginConfig{
833+
DefaultDialer: testDefaultDialer,
834+
TCPWriteTimeout: 1 * time.Second,
835+
}, &log)
826836

827837
datagramConn := &datagramV2Connection{
828838
conn,
829839
index,
830840
sessionManager,
831841
cfdflow.NewLimiter(0),
832842
datagramMuxer,
833-
ingress.DefaultUDPDialer,
843+
originDialer,
834844
packetRouter,
835845
15 * time.Second,
836846
0 * time.Second,

connection/quic_datagram_v2.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ type datagramV2Connection struct {
5757

5858
// datagramMuxer mux/demux datagrams from quic connection
5959
datagramMuxer *cfdquic.DatagramMuxerV2
60-
// ingressUDPProxy acts as the origin dialer for UDP requests
61-
ingressUDPProxy ingress.UDPOriginProxy
60+
// originDialer is the origin dialer for UDP requests
61+
originDialer ingress.OriginUDPDialer
6262
// packetRouter acts as the origin router for ICMP requests
6363
packetRouter *ingress.PacketRouter
6464

@@ -70,7 +70,7 @@ type datagramV2Connection struct {
7070

7171
func NewDatagramV2Connection(ctx context.Context,
7272
conn quic.Connection,
73-
ingressUDPProxy ingress.UDPOriginProxy,
73+
originDialer ingress.OriginUDPDialer,
7474
icmpRouter ingress.ICMPRouter,
7575
index uint8,
7676
rpcTimeout time.Duration,
@@ -89,7 +89,7 @@ func NewDatagramV2Connection(ctx context.Context,
8989
sessionManager: sessionManager,
9090
flowLimiter: flowLimiter,
9191
datagramMuxer: datagramMuxer,
92-
ingressUDPProxy: ingressUDPProxy,
92+
originDialer: originDialer,
9393
packetRouter: packetRouter,
9494
rpcTimeout: rpcTimeout,
9595
streamWriteTimeout: streamWriteTimeout,
@@ -159,7 +159,7 @@ func (q *datagramV2Connection) RegisterUdpSession(ctx context.Context, sessionID
159159

160160
// Each session is a series of datagram from an eyeball to a dstIP:dstPort.
161161
// (src port, dst IP, dst port) uniquely identifies a session, so it needs a dedicated connected socket.
162-
originProxy, err := q.ingressUDPProxy.DialUDP(dstAddrPort)
162+
originProxy, err := q.originDialer.DialUDP(dstAddrPort)
163163
if err != nil {
164164
log.Err(err).Msgf("Failed to create udp proxy to %s", dstAddrPort)
165165
tracing.EndWithErrorStatus(registerSpan, err)

connection/quic_datagram_v2_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
"go.uber.org/mock/gomock"
1414

1515
cfdflow "github.com/cloudflare/cloudflared/flow"
16-
"github.com/cloudflare/cloudflared/ingress"
1716
"github.com/cloudflare/cloudflared/mocks"
1817
)
1918

@@ -84,7 +83,7 @@ func TestRateLimitOnNewDatagramV2UDPSession(t *testing.T) {
8483
datagramConn := NewDatagramV2Connection(
8584
t.Context(),
8685
conn,
87-
ingress.DefaultUDPDialer,
86+
nil,
8887
nil,
8988
0,
9089
0*time.Second,

ingress/origin_connection.go

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import (
1919
type OriginConnection interface {
2020
// Stream should generally be implemented as a bidirectional io.Copy.
2121
Stream(ctx context.Context, tunnelConn io.ReadWriter, log *zerolog.Logger)
22-
Close()
22+
Close() error
2323
}
2424

2525
type streamHandlerFunc func(originConn io.ReadWriter, remoteConn net.Conn, log *zerolog.Logger)
@@ -48,16 +48,7 @@ func (tc *tcpConnection) Write(b []byte) (int, error) {
4848
}
4949
}
5050

51-
nBytes, err := tc.Conn.Write(b)
52-
if err != nil {
53-
tc.logger.Err(err).Msg("Error writing to the TCP connection")
54-
}
55-
56-
return nBytes, err
57-
}
58-
59-
func (tc *tcpConnection) Close() {
60-
tc.Conn.Close()
51+
return tc.Conn.Write(b)
6152
}
6253

6354
// tcpOverWSConnection is an OriginConnection that streams to TCP over WS.
@@ -75,8 +66,8 @@ func (wc *tcpOverWSConnection) Stream(ctx context.Context, tunnelConn io.ReadWri
7566
wsConn.Close()
7667
}
7768

78-
func (wc *tcpOverWSConnection) Close() {
79-
wc.conn.Close()
69+
func (wc *tcpOverWSConnection) Close() error {
70+
return wc.conn.Close()
8071
}
8172

8273
// socksProxyOverWSConnection is an OriginConnection that streams SOCKS connections over WS.
@@ -95,5 +86,6 @@ func (sp *socksProxyOverWSConnection) Stream(ctx context.Context, tunnelConn io.
9586
wsConn.Close()
9687
}
9788

98-
func (sp *socksProxyOverWSConnection) Close() {
89+
func (sp *socksProxyOverWSConnection) Close() error {
90+
return nil
9991
}

ingress/origin_dialer.go

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package ingress
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net"
7+
"net/netip"
8+
"sync"
9+
"time"
10+
11+
"github.com/rs/zerolog"
12+
)
13+
14+
// OriginTCPDialer provides a TCP dial operation to a requested address.
15+
type OriginTCPDialer interface {
16+
DialTCP(ctx context.Context, addr netip.AddrPort) (net.Conn, error)
17+
}
18+
19+
// OriginUDPDialer provides a UDP dial operation to a requested address.
20+
type OriginUDPDialer interface {
21+
DialUDP(addr netip.AddrPort) (net.Conn, error)
22+
}
23+
24+
// OriginDialer provides both TCP and UDP dial operations to an address.
25+
type OriginDialer interface {
26+
OriginTCPDialer
27+
OriginUDPDialer
28+
}
29+
30+
type OriginConfig struct {
31+
// The default Dialer used if no reserved services are found for an origin request.
32+
DefaultDialer OriginDialer
33+
// Timeout on write operations for TCP connections to the origin.
34+
TCPWriteTimeout time.Duration
35+
}
36+
37+
// OriginDialerService provides a proxy TCP and UDP dialer to origin services while allowing reserved
38+
// services to be provided. These reserved services are assigned to specific [netip.AddrPort]s
39+
// and provide their own [OriginDialer]'s to handle origin dialing per protocol.
40+
type OriginDialerService struct {
41+
// Reserved TCP services for reserved AddrPort values
42+
reservedTCPServices map[netip.AddrPort]OriginTCPDialer
43+
// Reserved UDP services for reserved AddrPort values
44+
reservedUDPServices map[netip.AddrPort]OriginUDPDialer
45+
// The default Dialer used if no reserved services are found for an origin request
46+
defaultDialer OriginDialer
47+
defaultDialerM sync.RWMutex
48+
// Write timeout for TCP connections
49+
writeTimeout time.Duration
50+
51+
logger *zerolog.Logger
52+
}
53+
54+
func NewOriginDialer(config OriginConfig, logger *zerolog.Logger) *OriginDialerService {
55+
return &OriginDialerService{
56+
reservedTCPServices: map[netip.AddrPort]OriginTCPDialer{},
57+
reservedUDPServices: map[netip.AddrPort]OriginUDPDialer{},
58+
defaultDialer: config.DefaultDialer,
59+
writeTimeout: config.TCPWriteTimeout,
60+
logger: logger,
61+
}
62+
}
63+
64+
// AddReservedService adds a reserved virtual service to dial to.
65+
// Not locked and expected to be initialized before calling first dial and not afterwards.
66+
func (d *OriginDialerService) AddReservedService(service OriginDialer, addrs []netip.AddrPort) {
67+
for _, addr := range addrs {
68+
d.reservedTCPServices[addr] = service
69+
d.reservedUDPServices[addr] = service
70+
}
71+
}
72+
73+
// UpdateDefaultDialer updates the default dialer.
74+
func (d *OriginDialerService) UpdateDefaultDialer(dialer *Dialer) {
75+
d.defaultDialerM.Lock()
76+
defer d.defaultDialerM.Unlock()
77+
d.defaultDialer = dialer
78+
}
79+
80+
// DialTCP will perform a dial TCP to the requested addr.
81+
func (d *OriginDialerService) DialTCP(ctx context.Context, addr netip.AddrPort) (net.Conn, error) {
82+
conn, err := d.dialTCP(ctx, addr)
83+
if err != nil {
84+
return nil, err
85+
}
86+
// Assign the write timeout for the TCP operations
87+
return &tcpConnection{
88+
Conn: conn,
89+
writeTimeout: d.writeTimeout,
90+
logger: d.logger,
91+
}, nil
92+
}
93+
94+
func (d *OriginDialerService) dialTCP(ctx context.Context, addr netip.AddrPort) (net.Conn, error) {
95+
// Check to see if any reserved services are available for this addr and call their dialer instead.
96+
if dialer, ok := d.reservedTCPServices[addr]; ok {
97+
return dialer.DialTCP(ctx, addr)
98+
}
99+
d.defaultDialerM.RLock()
100+
dialer := d.defaultDialer
101+
d.defaultDialerM.RUnlock()
102+
return dialer.DialTCP(ctx, addr)
103+
}
104+
105+
// DialUDP will perform a dial UDP to the requested addr.
106+
func (d *OriginDialerService) DialUDP(addr netip.AddrPort) (net.Conn, error) {
107+
// Check to see if any reserved services are available for this addr and call their dialer instead.
108+
if dialer, ok := d.reservedUDPServices[addr]; ok {
109+
return dialer.DialUDP(addr)
110+
}
111+
d.defaultDialerM.RLock()
112+
dialer := d.defaultDialer
113+
d.defaultDialerM.RUnlock()
114+
return dialer.DialUDP(addr)
115+
}
116+
117+
type Dialer struct {
118+
Dialer net.Dialer
119+
}
120+
121+
func NewDialer(config WarpRoutingConfig) *Dialer {
122+
return &Dialer{
123+
Dialer: net.Dialer{
124+
Timeout: config.ConnectTimeout.Duration,
125+
KeepAlive: config.TCPKeepAlive.Duration,
126+
},
127+
}
128+
}
129+
130+
func (d *Dialer) DialTCP(ctx context.Context, dest netip.AddrPort) (net.Conn, error) {
131+
conn, err := d.Dialer.DialContext(ctx, "tcp", dest.String())
132+
if err != nil {
133+
return nil, fmt.Errorf("unable to dial tcp to origin %s: %w", dest, err)
134+
}
135+
136+
return conn, nil
137+
}
138+
139+
func (d *Dialer) DialUDP(dest netip.AddrPort) (net.Conn, error) {
140+
conn, err := d.Dialer.Dial("udp", dest.String())
141+
if err != nil {
142+
return nil, fmt.Errorf("unable to dial udp to origin %s: %w", dest, err)
143+
}
144+
145+
return conn, nil
146+
}

ingress/origin_udp_proxy.go

Lines changed: 0 additions & 66 deletions
This file was deleted.

0 commit comments

Comments
 (0)