Skip to content

Commit e9d07e3

Browse files
committed
TUN-6861: Trace ICMP on Windows
1 parent 2d5234e commit e9d07e3

File tree

3 files changed

+77
-21
lines changed

3 files changed

+77
-21
lines changed

ingress/icmp_windows.go

Lines changed: 66 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ import (
2121
"github.com/google/gopacket/layers"
2222
"github.com/pkg/errors"
2323
"github.com/rs/zerolog"
24+
"go.opentelemetry.io/otel/attribute"
2425
"golang.org/x/net/icmp"
2526
"golang.org/x/net/ipv4"
2627
"golang.org/x/net/ipv6"
2728

2829
"github.com/cloudflare/cloudflared/packet"
30+
"github.com/cloudflare/cloudflared/tracing"
2931
)
3032

3133
const (
@@ -266,33 +268,50 @@ func (ip *icmpProxy) Serve(ctx context.Context) error {
266268
// The async version of Win32 APIs take a callback whose memory is not garbage collected, so we use the synchronous version.
267269
// It's possible that a slow request will block other requests, so we set the timeout to only 1s.
268270
func (ip *icmpProxy) Request(ctx context.Context, pk *packet.ICMP, responder *packetResponder) error {
269-
if pk == nil {
270-
return errPacketNil
271-
}
272271
defer func() {
273272
if r := recover(); r != nil {
274273
ip.logger.Error().Interface("error", r).Msgf("Recover panic from sending icmp request/response, error %s", debug.Stack())
275274
}
276275
}()
277276

277+
_, requestSpan := responder.requestSpan(ctx, pk)
278+
defer responder.exportSpan()
279+
278280
echo, err := getICMPEcho(pk.Message)
279281
if err != nil {
280282
return err
281283
}
282-
respData, err := ip.icmpEchoRoundtrip(pk.Dst, echo)
284+
requestSpan.SetAttributes(
285+
attribute.Int("originalEchoID", echo.ID),
286+
attribute.Int("seq", echo.Seq),
287+
)
288+
289+
resp, err := ip.icmpEchoRoundtrip(pk.Dst, echo)
283290
if err != nil {
284291
ip.logger.Err(err).Msg("ICMP echo roundtrip failed")
292+
tracing.EndWithErrorStatus(requestSpan, err)
285293
return err
286294
}
287-
288-
err = ip.handleEchoReply(pk, echo, respData, responder)
295+
tracing.End(requestSpan)
296+
responder.exportSpan()
297+
298+
_, 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+
)
305+
err = ip.handleEchoReply(pk, echo, resp, responder)
289306
if err != nil {
307+
tracing.EndWithErrorStatus(replySpan, err)
290308
return errors.Wrap(err, "failed to handle ICMP echo reply")
291309
}
310+
tracing.End(replySpan)
292311
return nil
293312
}
294313

295-
func (ip *icmpProxy) handleEchoReply(request *packet.ICMP, echoReq *icmp.Echo, data []byte, responder *packetResponder) error {
314+
func (ip *icmpProxy) handleEchoReply(request *packet.ICMP, echoReq *icmp.Echo, resp echoResp, responder *packetResponder) error {
296315
var replyType icmp.Type
297316
if request.Dst.Is4() {
298317
replyType = ipv4.ICMPTypeEchoReply
@@ -313,7 +332,7 @@ func (ip *icmpProxy) handleEchoReply(request *packet.ICMP, echoReq *icmp.Echo, d
313332
Body: &icmp.Echo{
314333
ID: echoReq.ID,
315334
Seq: echoReq.Seq,
316-
Data: data,
335+
Data: resp.payload(),
317336
},
318337
},
319338
}
@@ -334,16 +353,17 @@ func (ip *icmpProxy) handleEchoReply(request *packet.ICMP, echoReq *icmp.Echo, d
334353
return responder.returnPacket(serializedPacket)
335354
}
336355

337-
func (ip *icmpProxy) icmpEchoRoundtrip(dst netip.Addr, echo *icmp.Echo) ([]byte, error) {
356+
func (ip *icmpProxy) icmpEchoRoundtrip(dst netip.Addr, echo *icmp.Echo) (echoResp, error) {
338357
if dst.Is6() {
339358
if ip.srcSocketAddr == nil {
340359
return nil, fmt.Errorf("cannot send ICMPv6 using ICMPv4 proxy")
341360
}
342361
resp, err := ip.icmp6SendEcho(dst, echo)
343362
if err != nil {
363+
344364
return nil, errors.Wrap(err, "failed to send/receive ICMPv6 echo")
345365
}
346-
return resp.data, nil
366+
return resp, nil
347367
}
348368
if ip.srcSocketAddr != nil {
349369
return nil, fmt.Errorf("cannot send ICMPv4 using ICMPv6 proxy")
@@ -352,7 +372,7 @@ func (ip *icmpProxy) icmpEchoRoundtrip(dst netip.Addr, echo *icmp.Echo) ([]byte,
352372
if err != nil {
353373
return nil, errors.Wrap(err, "failed to send/receive ICMPv4 echo")
354374
}
355-
return resp.data, nil
375+
return resp, nil
356376
}
357377

358378
/*
@@ -371,7 +391,7 @@ func (ip *icmpProxy) icmpEchoRoundtrip(dst netip.Addr, echo *icmp.Echo) ([]byte,
371391
To retain the reference allocated objects, conversion from pointer to uintptr must happen as arguments to the
372392
syscall function
373393
*/
374-
func (ip *icmpProxy) icmpSendEcho(dst netip.Addr, echo *icmp.Echo) (*echoResp, error) {
394+
func (ip *icmpProxy) icmpSendEcho(dst netip.Addr, echo *icmp.Echo) (*echoV4Resp, error) {
375395
dataSize := len(echo.Data)
376396
replySize := echoReplySize + uintptr(dataSize)
377397
replyBuf := make([]byte, replySize)
@@ -399,7 +419,7 @@ func (ip *icmpProxy) icmpSendEcho(dst netip.Addr, echo *icmp.Echo) (*echoResp, e
399419
} else if replyCount > 1 {
400420
ip.logger.Warn().Msgf("Received %d ICMP echo replies, only sending 1 back", replyCount)
401421
}
402-
return newEchoResp(replyBuf)
422+
return newEchoV4Resp(replyBuf)
403423
}
404424

405425
// Third definition of https://docs.microsoft.com/en-us/windows/win32/api/inaddr/ns-inaddr-in_addr#syntax is address in uint32
@@ -411,12 +431,30 @@ func inAddrV4(ip netip.Addr) (uint32, error) {
411431
return endian.Uint32(v4[:]), nil
412432
}
413433

414-
type echoResp struct {
434+
type echoResp interface {
435+
status() ipStatus
436+
rtt() uint32
437+
payload() []byte
438+
}
439+
440+
type echoV4Resp struct {
415441
reply *echoReply
416442
data []byte
417443
}
418444

419-
func newEchoResp(replyBuf []byte) (*echoResp, error) {
445+
func (r *echoV4Resp) status() ipStatus {
446+
return r.reply.Status
447+
}
448+
449+
func (r *echoV4Resp) rtt() uint32 {
450+
return r.reply.RoundTripTime
451+
}
452+
453+
func (r *echoV4Resp) payload() []byte {
454+
return r.data
455+
}
456+
457+
func newEchoV4Resp(replyBuf []byte) (*echoV4Resp, error) {
420458
if len(replyBuf) == 0 {
421459
return nil, fmt.Errorf("reply buffer is empty")
422460
}
@@ -430,7 +468,7 @@ func newEchoResp(replyBuf []byte) (*echoResp, error) {
430468
if dataBufStart < int(echoReplySize) {
431469
return nil, fmt.Errorf("reply buffer size %d is too small to hold data of size %d", len(replyBuf), int(reply.DataSize))
432470
}
433-
return &echoResp{
471+
return &echoV4Resp{
434472
reply: &reply,
435473
data: replyBuf[dataBufStart:],
436474
}, nil
@@ -502,6 +540,18 @@ type echoV6Resp struct {
502540
data []byte
503541
}
504542

543+
func (r *echoV6Resp) status() ipStatus {
544+
return r.reply.Status
545+
}
546+
547+
func (r *echoV6Resp) rtt() uint32 {
548+
return r.reply.RoundTripTime
549+
}
550+
551+
func (r *echoV6Resp) payload() []byte {
552+
return r.data
553+
}
554+
505555
func newEchoV6Resp(replyBuf []byte, dataSize int) (*echoV6Resp, error) {
506556
if len(replyBuf) == 0 {
507557
return nil, fmt.Errorf("reply buffer is empty")

ingress/icmp_windows_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ func TestParseEchoReply(t *testing.T) {
5959
}
6060

6161
for _, test := range tests {
62-
resp, err := newEchoResp(test.replyBuf)
62+
resp, err := newEchoV4Resp(test.replyBuf)
6363
if test.expectedReply == nil {
6464
require.Error(t, err)
6565
require.Nil(t, resp)

ingress/origin_icmp_proxy_test.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,6 @@ func testICMPRouterEcho(t *testing.T, sendIPv4 bool) {
103103
}
104104

105105
func TestTraceICMPRouterEcho(t *testing.T) {
106-
if runtime.GOOS == "windows" {
107-
t.Skip("TODO: TUN-6861 Trace ICMP in Windows")
108-
}
109106
tracingCtx := "ec31ad8a01fde11fdcabe2efdce36873:52726f6cabc144f5:0:1"
110107

111108
router, err := NewICMPRouter(localhostIP, localhostIPv6, "", &noopLogger)
@@ -149,15 +146,24 @@ func TestTraceICMPRouterEcho(t *testing.T) {
149146
Body: echo,
150147
},
151148
}
149+
152150
require.NoError(t, router.Request(ctx, &pk, &responder))
153-
validateEchoFlow(t, muxer, &pk)
151+
// On Windows, request span is returned before reply
152+
if runtime.GOOS != "windows" {
153+
validateEchoFlow(t, muxer, &pk)
154+
}
154155

155156
// Request span
156157
receivedPacket := <-muxer.cfdToEdge
157158
requestSpan, ok := receivedPacket.(*quicpogs.TracingSpanPacket)
158159
require.True(t, ok)
159160
require.NotEmpty(t, requestSpan.Spans)
160161
require.True(t, bytes.Equal(serializedIdentity, requestSpan.TracingIdentity))
162+
163+
if runtime.GOOS == "windows" {
164+
validateEchoFlow(t, muxer, &pk)
165+
}
166+
161167
// Reply span
162168
receivedPacket = <-muxer.cfdToEdge
163169
replySpan, ok := receivedPacket.(*quicpogs.TracingSpanPacket)

0 commit comments

Comments
 (0)