Skip to content

Commit 9c1f5c3

Browse files
committed
TUN-8158: Bring back commit e653741 and fixes infinite loop on linux when the socket is closed
1 parent f75503b commit 9c1f5c3

File tree

15 files changed

+376
-64
lines changed

15 files changed

+376
-64
lines changed

cmd/cloudflared/tunnel/configuration.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ import (
3030
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
3131
)
3232

33-
const secretValue = "*****"
33+
const (
34+
secretValue = "*****"
35+
icmpFunnelTimeout = time.Second * 10
36+
)
3437

3538
var (
3639
developerPortal = "https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup"
@@ -361,7 +364,7 @@ func newPacketConfig(c *cli.Context, logger *zerolog.Logger) (*ingress.GlobalRou
361364
logger.Info().Msgf("ICMP proxy will use %s as source for IPv6", ipv6Src)
362365
}
363366

364-
icmpRouter, err := ingress.NewICMPRouter(ipv4Src, ipv6Src, zone, logger)
367+
icmpRouter, err := ingress.NewICMPRouter(ipv4Src, ipv6Src, zone, logger, icmpFunnelTimeout)
365368
if err != nil {
366369
return nil, err
367370
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ require (
6060
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
6161
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
6262
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
63+
github.com/fortytw2/leaktest v1.3.0 // indirect
6364
github.com/go-logr/logr v1.3.0 // indirect
6465
github.com/go-logr/stdr v1.2.2 // indirect
6566
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQD
8888
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
8989
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
9090
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
91+
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
92+
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
9193
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
9294
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
9395
github.com/getsentry/sentry-go v0.16.0 h1:owk+S+5XcgJLlGR/3+3s6N4d+uKwqYvh/eS0AIMjPWo=

ingress/icmp_darwin.go

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -131,18 +131,16 @@ func newICMPProxy(listenIP netip.Addr, zone string, logger *zerolog.Logger, idle
131131
}
132132

133133
func (ip *icmpProxy) Request(ctx context.Context, pk *packet.ICMP, responder *packetResponder) error {
134-
ctx, span := responder.requestSpan(ctx, pk)
134+
_, span := responder.requestSpan(ctx, pk)
135135
defer responder.exportSpan()
136136

137137
originalEcho, err := getICMPEcho(pk.Message)
138138
if err != nil {
139139
tracing.EndWithErrorStatus(span, err)
140140
return err
141141
}
142-
span.SetAttributes(
143-
attribute.Int("originalEchoID", originalEcho.ID),
144-
attribute.Int("seq", originalEcho.Seq),
145-
)
142+
observeICMPRequest(ip.logger, span, pk.Src.String(), pk.Dst.String(), originalEcho.ID, originalEcho.Seq)
143+
146144
echoIDTrackerKey := flow3Tuple{
147145
srcIP: pk.Src,
148146
dstIP: pk.Dst,
@@ -189,6 +187,7 @@ func (ip *icmpProxy) Request(ctx context.Context, pk *packet.ICMP, responder *pa
189187
tracing.EndWithErrorStatus(span, err)
190188
return err
191189
}
190+
192191
err = icmpFlow.sendToDst(pk.Dst, pk.Message)
193192
if err != nil {
194193
tracing.EndWithErrorStatus(span, err)
@@ -269,15 +268,12 @@ func (ip *icmpProxy) sendReply(ctx context.Context, reply *echoReply) error {
269268
_, span := icmpFlow.responder.replySpan(ctx, ip.logger)
270269
defer icmpFlow.responder.exportSpan()
271270

272-
span.SetAttributes(
273-
attribute.String("dst", reply.from.String()),
274-
attribute.Int("echoID", reply.echo.ID),
275-
attribute.Int("seq", reply.echo.Seq),
276-
attribute.Int("originalEchoID", icmpFlow.originalEchoID),
277-
)
278271
if err := icmpFlow.returnToSrc(reply); err != nil {
279272
tracing.EndWithErrorStatus(span, err)
273+
return err
280274
}
275+
observeICMPReply(ip.logger, span, reply.from.String(), reply.echo.ID, reply.echo.Seq)
276+
span.SetAttributes(attribute.Int("originalEchoID", icmpFlow.originalEchoID))
281277
tracing.End(span)
282278
return nil
283279
}

ingress/icmp_linux.go

Lines changed: 22 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,7 @@ func (ip *icmpProxy) Request(ctx context.Context, pk *packet.ICMP, responder *pa
107107
tracing.EndWithErrorStatus(span, err)
108108
return err
109109
}
110-
span.SetAttributes(
111-
attribute.Int("originalEchoID", originalEcho.ID),
112-
attribute.Int("seq", originalEcho.Seq),
113-
)
110+
observeICMPRequest(ip.logger, span, pk.Src.String(), pk.Dst.String(), originalEcho.ID, originalEcho.Seq)
114111

115112
shouldReplaceFunnelFunc := createShouldReplaceFunnelFunc(ip.logger, responder.datagramMuxer, pk, originalEcho.ID)
116113
newFunnelFunc := func() (packet.Funnel, error) {
@@ -156,14 +153,8 @@ func (ip *icmpProxy) Request(ctx context.Context, pk *packet.ICMP, responder *pa
156153
Int("originalEchoID", originalEcho.ID).
157154
Msg("New flow")
158155
go func() {
159-
defer ip.srcFunnelTracker.Unregister(funnelID, icmpFlow)
160-
if err := ip.listenResponse(ctx, icmpFlow); err != nil {
161-
ip.logger.Debug().Err(err).
162-
Str("src", pk.Src.String()).
163-
Str("dst", pk.Dst.String()).
164-
Int("originalEchoID", originalEcho.ID).
165-
Msg("Failed to listen for ICMP echo response")
166-
}
156+
ip.listenResponse(ctx, icmpFlow)
157+
ip.srcFunnelTracker.Unregister(funnelID, icmpFlow)
167158
}()
168159
}
169160
if err := icmpFlow.sendToDst(pk.Dst, pk.Message); err != nil {
@@ -179,17 +170,17 @@ func (ip *icmpProxy) Serve(ctx context.Context) error {
179170
return ctx.Err()
180171
}
181172

182-
func (ip *icmpProxy) listenResponse(ctx context.Context, flow *icmpEchoFlow) error {
173+
func (ip *icmpProxy) listenResponse(ctx context.Context, flow *icmpEchoFlow) {
183174
buf := make([]byte, mtu)
184175
for {
185-
retryable, err := ip.handleResponse(ctx, flow, buf)
186-
if err != nil && !retryable {
187-
return err
176+
if done := ip.handleResponse(ctx, flow, buf); done {
177+
return
188178
}
189179
}
190180
}
191181

192-
func (ip *icmpProxy) handleResponse(ctx context.Context, flow *icmpEchoFlow, buf []byte) (retryableErr bool, err error) {
182+
// Listens for ICMP response and handles error logging
183+
func (ip *icmpProxy) handleResponse(ctx context.Context, flow *icmpEchoFlow, buf []byte) (done bool) {
193184
_, span := flow.responder.replySpan(ctx, ip.logger)
194185
defer flow.responder.exportSpan()
195186

@@ -199,33 +190,36 @@ func (ip *icmpProxy) handleResponse(ctx context.Context, flow *icmpEchoFlow, buf
199190

200191
n, from, err := flow.originConn.ReadFrom(buf)
201192
if err != nil {
193+
if flow.IsClosed() {
194+
tracing.EndWithErrorStatus(span, fmt.Errorf("flow was closed"))
195+
return true
196+
}
197+
ip.logger.Error().Err(err).Str("socket", flow.originConn.LocalAddr().String()).Msg("Failed to read from ICMP socket")
202198
tracing.EndWithErrorStatus(span, err)
203-
return false, err
199+
return true
204200
}
205201
reply, err := parseReply(from, buf[:n])
206202
if err != nil {
207203
ip.logger.Error().Err(err).Str("dst", from.String()).Msg("Failed to parse ICMP reply")
208204
tracing.EndWithErrorStatus(span, err)
209-
return true, err
205+
return false
210206
}
211207
if !isEchoReply(reply.msg) {
212208
err := fmt.Errorf("Expect ICMP echo reply, got %s", reply.msg.Type)
213209
ip.logger.Debug().Str("dst", from.String()).Msgf("Drop ICMP %s from reply", reply.msg.Type)
214210
tracing.EndWithErrorStatus(span, err)
215-
return true, err
211+
return false
216212
}
217-
span.SetAttributes(
218-
attribute.String("dst", reply.from.String()),
219-
attribute.Int("echoID", reply.echo.ID),
220-
attribute.Int("seq", reply.echo.Seq),
221-
)
213+
222214
if err := flow.returnToSrc(reply); err != nil {
223-
ip.logger.Debug().Err(err).Str("dst", from.String()).Msg("Failed to send ICMP reply")
215+
ip.logger.Error().Err(err).Str("dst", from.String()).Msg("Failed to send ICMP reply")
224216
tracing.EndWithErrorStatus(span, err)
225-
return true, err
217+
return false
226218
}
219+
220+
observeICMPReply(ip.logger, span, from.String(), reply.echo.ID, reply.echo.Seq)
227221
tracing.End(span)
228-
return true, nil
222+
return false
229223
}
230224

231225
// Only linux uses flow3Tuple as FunnelID

ingress/icmp_posix.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"net"
1010
"net/netip"
11+
"sync/atomic"
1112

1213
"github.com/google/gopacket/layers"
1314
"github.com/rs/zerolog"
@@ -46,6 +47,7 @@ type flow3Tuple struct {
4647
type icmpEchoFlow struct {
4748
*packet.ActivityTracker
4849
closeCallback func() error
50+
closed *atomic.Bool
4951
src netip.Addr
5052
originConn *icmp.PacketConn
5153
responder *packetResponder
@@ -59,6 +61,7 @@ func newICMPEchoFlow(src netip.Addr, closeCallback func() error, originConn *icm
5961
return &icmpEchoFlow{
6062
ActivityTracker: packet.NewActivityTracker(),
6163
closeCallback: closeCallback,
64+
closed: &atomic.Bool{},
6265
src: src,
6366
originConn: originConn,
6467
responder: responder,
@@ -86,9 +89,14 @@ func (ief *icmpEchoFlow) Equal(other packet.Funnel) bool {
8689
}
8790

8891
func (ief *icmpEchoFlow) Close() error {
92+
ief.closed.Store(true)
8993
return ief.closeCallback()
9094
}
9195

96+
func (ief *icmpEchoFlow) IsClosed() bool {
97+
return ief.closed.Load()
98+
}
99+
92100
// sendToDst rewrites the echo ID to the one assigned to this flow
93101
func (ief *icmpEchoFlow) sendToDst(dst netip.Addr, msg *icmp.Message) error {
94102
ief.UpdateLastActive()

ingress/icmp_posix_test.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"testing"
99
"time"
1010

11+
"github.com/fortytw2/leaktest"
1112
"github.com/google/gopacket/layers"
1213
"github.com/rs/zerolog"
1314
"github.com/stretchr/testify/require"
@@ -18,6 +19,8 @@ import (
1819
)
1920

2021
func TestFunnelIdleTimeout(t *testing.T) {
22+
defer leaktest.Check(t)()
23+
2124
const (
2225
idleTimeout = time.Second
2326
echoID = 42573
@@ -73,13 +76,16 @@ func TestFunnelIdleTimeout(t *testing.T) {
7376
require.NoError(t, proxy.Request(ctx, &pk, &newResponder))
7477
validateEchoFlow(t, <-newMuxer.cfdToEdge, &pk)
7578

79+
time.Sleep(idleTimeout * 2)
7680
cancel()
7781
<-proxyDone
7882
}
7983

8084
func TestReuseFunnel(t *testing.T) {
85+
defer leaktest.Check(t)()
86+
8187
const (
82-
idleTimeout = time.Second
88+
idleTimeout = time.Millisecond * 100
8389
echoID = 42573
8490
startSeq = 8129
8591
)
@@ -135,6 +141,8 @@ func TestReuseFunnel(t *testing.T) {
135141
require.True(t, found)
136142
require.Equal(t, funnel1, funnel2)
137143

144+
time.Sleep(idleTimeout * 2)
145+
138146
cancel()
139147
<-proxyDone
140148
}

ingress/icmp_windows.go

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -281,10 +281,7 @@ func (ip *icmpProxy) Request(ctx context.Context, pk *packet.ICMP, responder *pa
281281
if err != nil {
282282
return err
283283
}
284-
requestSpan.SetAttributes(
285-
attribute.Int("originalEchoID", echo.ID),
286-
attribute.Int("seq", echo.Seq),
287-
)
284+
observeICMPRequest(ip.logger, requestSpan, pk.Src.String(), pk.Dst.String(), echo.ID, echo.Seq)
288285

289286
resp, err := ip.icmpEchoRoundtrip(pk.Dst, echo)
290287
if err != nil {
@@ -296,17 +293,17 @@ func (ip *icmpProxy) Request(ctx context.Context, pk *packet.ICMP, responder *pa
296293
responder.exportSpan()
297294

298295
_, replySpan := responder.replySpan(ctx, ip.logger)
299-
replySpan.SetAttributes(
300-
attribute.Int("originalEchoID", echo.ID),
301-
attribute.Int("seq", echo.Seq),
302-
attribute.Int64("rtt", int64(resp.rtt())),
303-
attribute.String("status", resp.status().String()),
304-
)
305296
err = ip.handleEchoReply(pk, echo, resp, responder)
306297
if err != nil {
298+
ip.logger.Err(err).Msg("Failed to send ICMP reply")
307299
tracing.EndWithErrorStatus(replySpan, err)
308300
return errors.Wrap(err, "failed to handle ICMP echo reply")
309301
}
302+
observeICMPReply(ip.logger, replySpan, pk.Dst.String(), echo.ID, echo.Seq)
303+
replySpan.SetAttributes(
304+
attribute.Int64("rtt", int64(resp.rtt())),
305+
attribute.String("status", resp.status().String()),
306+
)
310307
tracing.End(replySpan)
311308
return nil
312309
}

ingress/origin_icmp_proxy.go

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"time"
88

99
"github.com/rs/zerolog"
10+
"go.opentelemetry.io/otel/attribute"
11+
"go.opentelemetry.io/otel/trace"
1012
"golang.org/x/net/icmp"
1113
"golang.org/x/net/ipv4"
1214
"golang.org/x/net/ipv6"
@@ -15,9 +17,7 @@ import (
1517
)
1618

1719
const (
18-
// funnelIdleTimeout controls how long to wait to close a funnel without send/return
19-
funnelIdleTimeout = time.Second * 10
20-
mtu = 1500
20+
mtu = 1500
2121
// icmpRequestTimeoutMs controls how long to wait for a reply
2222
icmpRequestTimeoutMs = 1000
2323
)
@@ -32,8 +32,9 @@ type icmpRouter struct {
3232
}
3333

3434
// NewICMPRouter doesn't return an error if either ipv4 proxy or ipv6 proxy can be created. The machine might only
35-
// support one of them
36-
func NewICMPRouter(ipv4Addr, ipv6Addr netip.Addr, ipv6Zone string, logger *zerolog.Logger) (*icmpRouter, error) {
35+
// support one of them.
36+
// funnelIdleTimeout controls how long to wait to close a funnel without send/return
37+
func NewICMPRouter(ipv4Addr, ipv6Addr netip.Addr, ipv6Zone string, logger *zerolog.Logger, funnelIdleTimeout time.Duration) (*icmpRouter, error) {
3738
ipv4Proxy, ipv4Err := newICMPProxy(ipv4Addr, "", logger, funnelIdleTimeout)
3839
ipv6Proxy, ipv6Err := newICMPProxy(ipv6Addr, ipv6Zone, logger, funnelIdleTimeout)
3940
if ipv4Err != nil && ipv6Err != nil {
@@ -102,3 +103,25 @@ func getICMPEcho(msg *icmp.Message) (*icmp.Echo, error) {
102103
func isEchoReply(msg *icmp.Message) bool {
103104
return msg.Type == ipv4.ICMPTypeEchoReply || msg.Type == ipv6.ICMPTypeEchoReply
104105
}
106+
107+
func observeICMPRequest(logger *zerolog.Logger, span trace.Span, src string, dst string, echoID int, seq int) {
108+
logger.Debug().
109+
Str("src", src).
110+
Str("dst", dst).
111+
Int("originalEchoID", echoID).
112+
Int("originalEchoSeq", seq).
113+
Msg("Received ICMP request")
114+
span.SetAttributes(
115+
attribute.Int("originalEchoID", echoID),
116+
attribute.Int("seq", seq),
117+
)
118+
}
119+
120+
func observeICMPReply(logger *zerolog.Logger, span trace.Span, dst string, echoID int, seq int) {
121+
logger.Debug().Str("dst", dst).Int("echoID", echoID).Int("seq", seq).Msg("Sent ICMP reply to edge")
122+
span.SetAttributes(
123+
attribute.String("dst", dst),
124+
attribute.Int("echoID", echoID),
125+
attribute.Int("seq", seq),
126+
)
127+
}

0 commit comments

Comments
 (0)