Skip to content

Commit 9da15b5

Browse files
committed
TUN-8640: Refactor ICMPRouter to support new ICMPResponders
A new ICMPResponder interface is introduced to provide different implementations of how the ICMP flows should return to the QUIC connection muxer. Improves usages of netip.AddrPort to leverage the embedded zone field for IPv6 addresses. Closes TUN-8640
1 parent 46dc631 commit 9da15b5

19 files changed

+199
-236
lines changed

cmd/cloudflared/tunnel/cmd.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ func StartServer(
510510

511511
// Disable ICMP packet routing for quick tunnels
512512
if quickTunnelURL != "" {
513-
tunnelConfig.PacketConfig = nil
513+
tunnelConfig.ICMPRouterServer = nil
514514
}
515515

516516
internalRules := []ingress.Rule{}

cmd/cloudflared/tunnel/configuration.go

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -252,11 +252,11 @@ func prepareTunnelConfig(
252252
QUICConnectionLevelFlowControlLimit: c.Uint64(quicConnLevelFlowControlLimit),
253253
QUICStreamLevelFlowControlLimit: c.Uint64(quicStreamLevelFlowControlLimit),
254254
}
255-
packetConfig, err := newPacketConfig(c, log)
255+
icmpRouter, err := newICMPRouter(c, log)
256256
if err != nil {
257257
log.Warn().Err(err).Msg("ICMP proxy feature is disabled")
258258
} else {
259-
tunnelConfig.PacketConfig = packetConfig
259+
tunnelConfig.ICMPRouterServer = icmpRouter
260260
}
261261
orchestratorConfig := &orchestration.Config{
262262
Ingress: &ingressRules,
@@ -351,7 +351,7 @@ func adjustIPVersionByBindAddress(ipVersion allregions.ConfigIPVersion, ip net.I
351351
}
352352
}
353353

354-
func newPacketConfig(c *cli.Context, logger *zerolog.Logger) (*ingress.GlobalRouterConfig, error) {
354+
func newICMPRouter(c *cli.Context, logger *zerolog.Logger) (ingress.ICMPRouterServer, error) {
355355
ipv4Src, err := determineICMPv4Src(c.String("icmpv4-src"), logger)
356356
if err != nil {
357357
return nil, errors.Wrap(err, "failed to determine IPv4 source address for ICMP proxy")
@@ -368,16 +368,11 @@ func newPacketConfig(c *cli.Context, logger *zerolog.Logger) (*ingress.GlobalRou
368368
logger.Info().Msgf("ICMP proxy will use %s as source for IPv6", ipv6Src)
369369
}
370370

371-
icmpRouter, err := ingress.NewICMPRouter(ipv4Src, ipv6Src, zone, logger, icmpFunnelTimeout)
371+
icmpRouter, err := ingress.NewICMPRouter(ipv4Src, ipv6Src, logger, icmpFunnelTimeout)
372372
if err != nil {
373373
return nil, err
374374
}
375-
return &ingress.GlobalRouterConfig{
376-
ICMPRouter: icmpRouter,
377-
IPv4Src: ipv4Src,
378-
IPv6Src: ipv6Src,
379-
Zone: zone,
380-
}, nil
375+
return icmpRouter, nil
381376
}
382377

383378
func determineICMPv4Src(userDefinedSrc string, logger *zerolog.Logger) (netip.Addr, error) {
@@ -407,13 +402,12 @@ type interfaceIP struct {
407402

408403
func determineICMPv6Src(userDefinedSrc string, logger *zerolog.Logger, ipv4Src netip.Addr) (addr netip.Addr, zone string, err error) {
409404
if userDefinedSrc != "" {
410-
userDefinedIP, zone, _ := strings.Cut(userDefinedSrc, "%")
411-
addr, err := netip.ParseAddr(userDefinedIP)
405+
addr, err := netip.ParseAddr(userDefinedSrc)
412406
if err != nil {
413407
return netip.Addr{}, "", err
414408
}
415409
if addr.Is6() {
416-
return addr, zone, nil
410+
return addr, addr.Zone(), nil
417411
}
418412
return netip.Addr{}, "", fmt.Errorf("expect IPv6, but %s is IPv4", userDefinedSrc)
419413
}

connection/quic_connection.go

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"io"
88
"net"
99
"net/http"
10-
"net/netip"
1110
"strconv"
1211
"strings"
1312
"sync/atomic"
@@ -18,7 +17,6 @@ import (
1817
"github.com/rs/zerolog"
1918
"golang.org/x/sync/errgroup"
2019

21-
"github.com/cloudflare/cloudflared/packet"
2220
cfdquic "github.com/cloudflare/cloudflared/quic"
2321
"github.com/cloudflare/cloudflared/tracing"
2422
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
@@ -417,28 +415,3 @@ func (np *nopCloserReadWriter) Close() error {
417415

418416
return nil
419417
}
420-
421-
// muxerWrapper wraps DatagramMuxerV2 to satisfy the packet.FunnelUniPipe interface
422-
type muxerWrapper struct {
423-
muxer *cfdquic.DatagramMuxerV2
424-
}
425-
426-
func (rp *muxerWrapper) SendPacket(dst netip.Addr, pk packet.RawPacket) error {
427-
return rp.muxer.SendPacket(cfdquic.RawPacket(pk))
428-
}
429-
430-
func (rp *muxerWrapper) ReceivePacket(ctx context.Context) (packet.RawPacket, error) {
431-
pk, err := rp.muxer.ReceivePacket(ctx)
432-
if err != nil {
433-
return packet.RawPacket{}, err
434-
}
435-
rawPacket, ok := pk.(cfdquic.RawPacket)
436-
if ok {
437-
return packet.RawPacket(rawPacket), nil
438-
}
439-
return packet.RawPacket{}, fmt.Errorf("unexpected packet type %+v", pk)
440-
}
441-
442-
func (rp *muxerWrapper) Close() error {
443-
return nil
444-
}

connection/quic_connection_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -752,7 +752,7 @@ func testTunnelConnection(t *testing.T, serverAddr netip.AddrPort, index uint8)
752752
sessionDemuxChan := make(chan *packet.Session, 4)
753753
datagramMuxer := cfdquic.NewDatagramMuxerV2(conn, &log, sessionDemuxChan)
754754
sessionManager := datagramsession.NewManager(&log, datagramMuxer.SendToSession, sessionDemuxChan)
755-
packetRouter := ingress.NewPacketRouter(nil, datagramMuxer, &log)
755+
packetRouter := ingress.NewPacketRouter(nil, datagramMuxer, 0, &log)
756756

757757
datagramConn := &datagramV2Connection{
758758
conn,

connection/quic_datagram_v2.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,16 @@ type datagramV2Connection struct {
5454

5555
func NewDatagramV2Connection(ctx context.Context,
5656
conn quic.Connection,
57-
packetConfig *ingress.GlobalRouterConfig,
57+
icmpRouter ingress.ICMPRouter,
58+
index uint8,
5859
rpcTimeout time.Duration,
5960
streamWriteTimeout time.Duration,
6061
logger *zerolog.Logger,
6162
) DatagramSessionHandler {
6263
sessionDemuxChan := make(chan *packet.Session, demuxChanCapacity)
6364
datagramMuxer := cfdquic.NewDatagramMuxerV2(conn, logger, sessionDemuxChan)
6465
sessionManager := datagramsession.NewManager(logger, datagramMuxer.SendToSession, sessionDemuxChan)
65-
packetRouter := ingress.NewPacketRouter(packetConfig, datagramMuxer, logger)
66+
packetRouter := ingress.NewPacketRouter(icmpRouter, datagramMuxer, index, logger)
6667

6768
return &datagramV2Connection{
6869
conn,

ingress/icmp_darwin.go

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,8 @@ type icmpProxy struct {
2828
srcFunnelTracker *packet.FunnelTracker
2929
echoIDTracker *echoIDTracker
3030
conn *icmp.PacketConn
31-
// Response is handled in one-by-one, so encoder can be shared between funnels
32-
encoder *packet.Encoder
33-
logger *zerolog.Logger
34-
idleTimeout time.Duration
31+
logger *zerolog.Logger
32+
idleTimeout time.Duration
3533
}
3634

3735
// echoIDTracker tracks which ID has been assigned. It first loops through assignment from lastAssignment to then end,
@@ -114,25 +112,24 @@ func (snf echoFunnelID) String() string {
114112
return strconv.FormatUint(uint64(snf), 10)
115113
}
116114

117-
func newICMPProxy(listenIP netip.Addr, zone string, logger *zerolog.Logger, idleTimeout time.Duration) (*icmpProxy, error) {
118-
conn, err := newICMPConn(listenIP, zone)
115+
func newICMPProxy(listenIP netip.Addr, logger *zerolog.Logger, idleTimeout time.Duration) (*icmpProxy, error) {
116+
conn, err := newICMPConn(listenIP)
119117
if err != nil {
120118
return nil, err
121119
}
122120
logger.Info().Msgf("Created ICMP proxy listening on %s", conn.LocalAddr())
123121
return &icmpProxy{
124122
srcFunnelTracker: packet.NewFunnelTracker(),
125123
echoIDTracker: newEchoIDTracker(),
126-
encoder: packet.NewEncoder(),
127124
conn: conn,
128125
logger: logger,
129126
idleTimeout: idleTimeout,
130127
}, nil
131128
}
132129

133-
func (ip *icmpProxy) Request(ctx context.Context, pk *packet.ICMP, responder *packetResponder) error {
134-
_, span := responder.requestSpan(ctx, pk)
135-
defer responder.exportSpan()
130+
func (ip *icmpProxy) Request(ctx context.Context, pk *packet.ICMP, responder ICMPResponder) error {
131+
_, span := responder.RequestSpan(ctx, pk)
132+
defer responder.ExportSpan()
136133

137134
originalEcho, err := getICMPEcho(pk.Message)
138135
if err != nil {
@@ -154,7 +151,7 @@ func (ip *icmpProxy) Request(ctx context.Context, pk *packet.ICMP, responder *pa
154151
}
155152
span.SetAttributes(attribute.Int("assignedEchoID", int(assignedEchoID)))
156153

157-
shouldReplaceFunnelFunc := createShouldReplaceFunnelFunc(ip.logger, responder.datagramMuxer, pk, originalEcho.ID)
154+
shouldReplaceFunnelFunc := createShouldReplaceFunnelFunc(ip.logger, responder, pk, originalEcho.ID)
158155
newFunnelFunc := func() (packet.Funnel, error) {
159156
originalEcho, err := getICMPEcho(pk.Message)
160157
if err != nil {
@@ -164,7 +161,7 @@ func (ip *icmpProxy) Request(ctx context.Context, pk *packet.ICMP, responder *pa
164161
ip.echoIDTracker.release(echoIDTrackerKey, assignedEchoID)
165162
return nil
166163
}
167-
icmpFlow := newICMPEchoFlow(pk.Src, closeCallback, ip.conn, responder, int(assignedEchoID), originalEcho.ID, ip.encoder)
164+
icmpFlow := newICMPEchoFlow(pk.Src, closeCallback, ip.conn, responder, int(assignedEchoID), originalEcho.ID)
168165
return icmpFlow, nil
169166
}
170167
funnelID := echoFunnelID(assignedEchoID)
@@ -265,8 +262,8 @@ func (ip *icmpProxy) sendReply(ctx context.Context, reply *echoReply) error {
265262
return err
266263
}
267264

268-
_, span := icmpFlow.responder.replySpan(ctx, ip.logger)
269-
defer icmpFlow.responder.exportSpan()
265+
_, span := icmpFlow.responder.ReplySpan(ctx, ip.logger)
266+
defer icmpFlow.responder.ExportSpan()
270267

271268
if err := icmpFlow.returnToSrc(reply); err != nil {
272269
tracing.EndWithErrorStatus(span, err)

ingress/icmp_generic.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@ var errICMPProxyNotImplemented = fmt.Errorf("ICMP proxy is not implemented on %s
1818

1919
type icmpProxy struct{}
2020

21-
func (ip icmpProxy) Request(ctx context.Context, pk *packet.ICMP, responder *packetResponder) error {
21+
func (ip icmpProxy) Request(ctx context.Context, pk *packet.ICMP, responder ICMPResponder) error {
2222
return errICMPProxyNotImplemented
2323
}
2424

2525
func (ip *icmpProxy) Serve(ctx context.Context) error {
2626
return errICMPProxyNotImplemented
2727
}
2828

29-
func newICMPProxy(listenIP netip.Addr, zone string, logger *zerolog.Logger, idleTimeout time.Duration) (*icmpProxy, error) {
29+
func newICMPProxy(listenIP netip.Addr, logger *zerolog.Logger, idleTimeout time.Duration) (*icmpProxy, error) {
3030
return nil, errICMPProxyNotImplemented
3131
}

ingress/icmp_linux.go

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,25 +37,23 @@ var (
3737
type icmpProxy struct {
3838
srcFunnelTracker *packet.FunnelTracker
3939
listenIP netip.Addr
40-
ipv6Zone string
4140
logger *zerolog.Logger
4241
idleTimeout time.Duration
4342
}
4443

45-
func newICMPProxy(listenIP netip.Addr, zone string, logger *zerolog.Logger, idleTimeout time.Duration) (*icmpProxy, error) {
46-
if err := testPermission(listenIP, zone, logger); err != nil {
44+
func newICMPProxy(listenIP netip.Addr, logger *zerolog.Logger, idleTimeout time.Duration) (*icmpProxy, error) {
45+
if err := testPermission(listenIP, logger); err != nil {
4746
return nil, err
4847
}
4948
return &icmpProxy{
5049
srcFunnelTracker: packet.NewFunnelTracker(),
5150
listenIP: listenIP,
52-
ipv6Zone: zone,
5351
logger: logger,
5452
idleTimeout: idleTimeout,
5553
}, nil
5654
}
5755

58-
func testPermission(listenIP netip.Addr, zone string, logger *zerolog.Logger) error {
56+
func testPermission(listenIP netip.Addr, logger *zerolog.Logger) error {
5957
// Opens a non-privileged ICMP socket. On Linux the group ID of the process needs to be in ping_group_range
6058
// Only check ping_group_range once for IPv4
6159
if listenIP.Is4() {
@@ -64,7 +62,7 @@ func testPermission(listenIP netip.Addr, zone string, logger *zerolog.Logger) er
6462
return err
6563
}
6664
}
67-
conn, err := newICMPConn(listenIP, zone)
65+
conn, err := newICMPConn(listenIP)
6866
if err != nil {
6967
return err
7068
}
@@ -98,9 +96,9 @@ func checkInPingGroup() error {
9896
return fmt.Errorf("did not find group range in %s", pingGroupPath)
9997
}
10098

101-
func (ip *icmpProxy) Request(ctx context.Context, pk *packet.ICMP, responder *packetResponder) error {
102-
ctx, span := responder.requestSpan(ctx, pk)
103-
defer responder.exportSpan()
99+
func (ip *icmpProxy) Request(ctx context.Context, pk *packet.ICMP, responder ICMPResponder) error {
100+
ctx, span := responder.RequestSpan(ctx, pk)
101+
defer responder.ExportSpan()
104102

105103
originalEcho, err := getICMPEcho(pk.Message)
106104
if err != nil {
@@ -109,9 +107,9 @@ func (ip *icmpProxy) Request(ctx context.Context, pk *packet.ICMP, responder *pa
109107
}
110108
observeICMPRequest(ip.logger, span, pk.Src.String(), pk.Dst.String(), originalEcho.ID, originalEcho.Seq)
111109

112-
shouldReplaceFunnelFunc := createShouldReplaceFunnelFunc(ip.logger, responder.datagramMuxer, pk, originalEcho.ID)
110+
shouldReplaceFunnelFunc := createShouldReplaceFunnelFunc(ip.logger, responder, pk, originalEcho.ID)
113111
newFunnelFunc := func() (packet.Funnel, error) {
114-
conn, err := newICMPConn(ip.listenIP, ip.ipv6Zone)
112+
conn, err := newICMPConn(ip.listenIP)
115113
if err != nil {
116114
tracing.EndWithErrorStatus(span, err)
117115
return nil, errors.Wrap(err, "failed to open ICMP socket")
@@ -127,7 +125,7 @@ func (ip *icmpProxy) Request(ctx context.Context, pk *packet.ICMP, responder *pa
127125
span.SetAttributes(attribute.Int("port", localUDPAddr.Port))
128126

129127
echoID := localUDPAddr.Port
130-
icmpFlow := newICMPEchoFlow(pk.Src, closeCallback, conn, responder, echoID, originalEcho.ID, packet.NewEncoder())
128+
icmpFlow := newICMPEchoFlow(pk.Src, closeCallback, conn, responder, echoID, originalEcho.ID)
131129
return icmpFlow, nil
132130
}
133131
funnelID := flow3Tuple{
@@ -181,8 +179,8 @@ func (ip *icmpProxy) listenResponse(ctx context.Context, flow *icmpEchoFlow) {
181179

182180
// Listens for ICMP response and handles error logging
183181
func (ip *icmpProxy) handleResponse(ctx context.Context, flow *icmpEchoFlow, buf []byte) (done bool) {
184-
_, span := flow.responder.replySpan(ctx, ip.logger)
185-
defer flow.responder.exportSpan()
182+
_, span := flow.responder.ReplySpan(ctx, ip.logger)
183+
defer flow.responder.ExportSpan()
186184

187185
span.SetAttributes(
188186
attribute.Int("originalEchoID", flow.originalEchoID),

ingress/icmp_posix.go

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,20 @@ import (
1818
)
1919

2020
// Opens a non-privileged ICMP socket on Linux and Darwin
21-
func newICMPConn(listenIP netip.Addr, zone string) (*icmp.PacketConn, error) {
21+
func newICMPConn(listenIP netip.Addr) (*icmp.PacketConn, error) {
2222
if listenIP.Is4() {
2323
return icmp.ListenPacket("udp4", listenIP.String())
2424
}
25-
listenAddr := listenIP.String()
26-
if zone != "" {
27-
listenAddr = listenAddr + "%" + zone
28-
}
29-
return icmp.ListenPacket("udp6", listenAddr)
25+
return icmp.ListenPacket("udp6", listenIP.String())
3026
}
3127

3228
func netipAddr(addr net.Addr) (netip.Addr, bool) {
3329
udpAddr, ok := addr.(*net.UDPAddr)
3430
if !ok {
3531
return netip.Addr{}, false
3632
}
37-
return netip.AddrFromSlice(udpAddr.IP)
33+
34+
return udpAddr.AddrPort().Addr(), true
3835
}
3936

4037
type flow3Tuple struct {
@@ -50,14 +47,12 @@ type icmpEchoFlow struct {
5047
closed *atomic.Bool
5148
src netip.Addr
5249
originConn *icmp.PacketConn
53-
responder *packetResponder
50+
responder ICMPResponder
5451
assignedEchoID int
5552
originalEchoID int
56-
// it's up to the user to ensure respEncoder is not used concurrently
57-
respEncoder *packet.Encoder
5853
}
5954

60-
func newICMPEchoFlow(src netip.Addr, closeCallback func() error, originConn *icmp.PacketConn, responder *packetResponder, assignedEchoID, originalEchoID int, respEncoder *packet.Encoder) *icmpEchoFlow {
55+
func newICMPEchoFlow(src netip.Addr, closeCallback func() error, originConn *icmp.PacketConn, responder ICMPResponder, assignedEchoID, originalEchoID int) *icmpEchoFlow {
6156
return &icmpEchoFlow{
6257
ActivityTracker: packet.NewActivityTracker(),
6358
closeCallback: closeCallback,
@@ -67,7 +62,6 @@ func newICMPEchoFlow(src netip.Addr, closeCallback func() error, originConn *icm
6762
responder: responder,
6863
assignedEchoID: assignedEchoID,
6964
originalEchoID: originalEchoID,
70-
respEncoder: respEncoder,
7165
}
7266
}
7367

@@ -139,11 +133,7 @@ func (ief *icmpEchoFlow) returnToSrc(reply *echoReply) error {
139133
},
140134
Message: reply.msg,
141135
}
142-
serializedPacket, err := ief.respEncoder.Encode(&pk)
143-
if err != nil {
144-
return err
145-
}
146-
return ief.responder.returnPacket(serializedPacket)
136+
return ief.responder.ReturnPacket(&pk)
147137
}
148138

149139
type echoReply struct {
@@ -184,7 +174,7 @@ func toICMPEchoFlow(funnel packet.Funnel) (*icmpEchoFlow, error) {
184174
return icmpFlow, nil
185175
}
186176

187-
func createShouldReplaceFunnelFunc(logger *zerolog.Logger, muxer muxer, pk *packet.ICMP, originalEchoID int) func(packet.Funnel) bool {
177+
func createShouldReplaceFunnelFunc(logger *zerolog.Logger, responder ICMPResponder, pk *packet.ICMP, originalEchoID int) func(packet.Funnel) bool {
188178
return func(existing packet.Funnel) bool {
189179
existingFlow, err := toICMPEchoFlow(existing)
190180
if err != nil {
@@ -199,7 +189,7 @@ func createShouldReplaceFunnelFunc(logger *zerolog.Logger, muxer muxer, pk *pack
199189
// If the existing flow has a different muxer, there's a new quic connection where return packets should be
200190
// routed. Otherwise, return packets will be send to the first observed incoming connection, rather than the
201191
// most recently observed connection.
202-
if existingFlow.responder.datagramMuxer != muxer {
192+
if existingFlow.responder.ConnectionIndex() != responder.ConnectionIndex() {
203193
logger.Debug().
204194
Str("src", pk.Src.String()).
205195
Str("dst", pk.Dst.String()).

0 commit comments

Comments
 (0)