172 lines
5.1 KiB
Go
172 lines
5.1 KiB
Go
package ingress
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/netip"
|
|
"time"
|
|
|
|
"github.com/rs/zerolog"
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/trace"
|
|
"golang.org/x/net/icmp"
|
|
"golang.org/x/net/ipv4"
|
|
"golang.org/x/net/ipv6"
|
|
|
|
"github.com/cloudflare/cloudflared/packet"
|
|
"github.com/cloudflare/cloudflared/tracing"
|
|
)
|
|
|
|
const (
|
|
mtu = 1500
|
|
// icmpRequestTimeoutMs controls how long to wait for a reply
|
|
icmpRequestTimeoutMs = 1000
|
|
)
|
|
|
|
var (
|
|
errPacketNil = fmt.Errorf("packet is nil")
|
|
)
|
|
|
|
// ICMPRouterServer is a parent interface over-top of ICMPRouter that allows for the operation of the proxy origin listeners.
|
|
type ICMPRouterServer interface {
|
|
ICMPRouter
|
|
// Serve runs the ICMPRouter proxy origin listeners for any of the IPv4 or IPv6 interfaces configured.
|
|
Serve(ctx context.Context) error
|
|
}
|
|
|
|
// ICMPRouter manages out-going ICMP requests towards the origin.
|
|
type ICMPRouter interface {
|
|
// Request will send an ICMP packet towards the origin with an ICMPResponder to attach to the ICMP flow for the
|
|
// response to utilize.
|
|
Request(ctx context.Context, pk *packet.ICMP, responder ICMPResponder) error
|
|
// ConvertToTTLExceeded will take an ICMP packet and create a ICMP TTL Exceeded response origininating from the
|
|
// ICMPRouter's IP interface.
|
|
ConvertToTTLExceeded(pk *packet.ICMP, rawPacket packet.RawPacket) *packet.ICMP
|
|
}
|
|
|
|
// ICMPResponder manages how to handle incoming ICMP messages coming from the origin to the edge.
|
|
type ICMPResponder interface {
|
|
ConnectionID() uint8
|
|
ReturnPacket(pk *packet.ICMP) error
|
|
AddTraceContext(tracedCtx *tracing.TracedContext, serializedIdentity []byte)
|
|
RequestSpan(ctx context.Context, pk *packet.ICMP) (context.Context, trace.Span)
|
|
ReplySpan(ctx context.Context, logger *zerolog.Logger) (context.Context, trace.Span)
|
|
ExportSpan()
|
|
}
|
|
|
|
type icmpRouter struct {
|
|
ipv4Proxy *icmpProxy
|
|
ipv4Src netip.Addr
|
|
ipv6Proxy *icmpProxy
|
|
ipv6Src netip.Addr
|
|
}
|
|
|
|
// NewICMPRouter doesn't return an error if either ipv4 proxy or ipv6 proxy can be created. The machine might only
|
|
// support one of them.
|
|
// funnelIdleTimeout controls how long to wait to close a funnel without send/return
|
|
func NewICMPRouter(ipv4Addr, ipv6Addr netip.Addr, logger *zerolog.Logger, funnelIdleTimeout time.Duration) (ICMPRouterServer, error) {
|
|
ipv4Proxy, ipv4Err := newICMPProxy(ipv4Addr, logger, funnelIdleTimeout)
|
|
ipv6Proxy, ipv6Err := newICMPProxy(ipv6Addr, logger, funnelIdleTimeout)
|
|
if ipv4Err != nil && ipv6Err != nil {
|
|
err := fmt.Errorf("cannot create ICMPv4 proxy: %v nor ICMPv6 proxy: %v", ipv4Err, ipv6Err)
|
|
logger.Debug().Err(err).Msg("ICMP proxy feature is disabled")
|
|
return nil, err
|
|
}
|
|
if ipv4Err != nil {
|
|
logger.Debug().Err(ipv4Err).Msg("failed to create ICMPv4 proxy, only ICMPv6 proxy is created")
|
|
ipv4Proxy = nil
|
|
}
|
|
if ipv6Err != nil {
|
|
logger.Debug().Err(ipv6Err).Msg("failed to create ICMPv6 proxy, only ICMPv4 proxy is created")
|
|
ipv6Proxy = nil
|
|
}
|
|
return &icmpRouter{
|
|
ipv4Proxy: ipv4Proxy,
|
|
ipv4Src: ipv4Addr,
|
|
ipv6Proxy: ipv6Proxy,
|
|
ipv6Src: ipv6Addr,
|
|
}, nil
|
|
}
|
|
|
|
func (ir *icmpRouter) Serve(ctx context.Context) error {
|
|
if ir.ipv4Proxy != nil && ir.ipv6Proxy != nil {
|
|
errC := make(chan error, 2)
|
|
go func() {
|
|
errC <- ir.ipv4Proxy.Serve(ctx)
|
|
}()
|
|
go func() {
|
|
errC <- ir.ipv6Proxy.Serve(ctx)
|
|
}()
|
|
return <-errC
|
|
}
|
|
if ir.ipv4Proxy != nil {
|
|
return ir.ipv4Proxy.Serve(ctx)
|
|
}
|
|
if ir.ipv6Proxy != nil {
|
|
return ir.ipv6Proxy.Serve(ctx)
|
|
}
|
|
return fmt.Errorf("ICMPv4 proxy and ICMPv6 proxy are both nil")
|
|
}
|
|
|
|
func (ir *icmpRouter) Request(ctx context.Context, pk *packet.ICMP, responder ICMPResponder) error {
|
|
if pk == nil {
|
|
return errPacketNil
|
|
}
|
|
if pk.Dst.Is4() {
|
|
if ir.ipv4Proxy != nil {
|
|
return ir.ipv4Proxy.Request(ctx, pk, responder)
|
|
}
|
|
return fmt.Errorf("ICMPv4 proxy was not instantiated")
|
|
}
|
|
if ir.ipv6Proxy != nil {
|
|
return ir.ipv6Proxy.Request(ctx, pk, responder)
|
|
}
|
|
return fmt.Errorf("ICMPv6 proxy was not instantiated")
|
|
}
|
|
|
|
func (ir *icmpRouter) ConvertToTTLExceeded(pk *packet.ICMP, rawPacket packet.RawPacket) *packet.ICMP {
|
|
var srcIP netip.Addr
|
|
if pk.Dst.Is4() {
|
|
srcIP = ir.ipv4Src
|
|
} else {
|
|
srcIP = ir.ipv6Src
|
|
}
|
|
return packet.NewICMPTTLExceedPacket(pk.IP, rawPacket, srcIP)
|
|
}
|
|
|
|
func getICMPEcho(msg *icmp.Message) (*icmp.Echo, error) {
|
|
echo, ok := msg.Body.(*icmp.Echo)
|
|
if !ok {
|
|
return nil, fmt.Errorf("expect ICMP echo, got %s", msg.Type)
|
|
}
|
|
return echo, nil
|
|
}
|
|
|
|
func isEchoReply(msg *icmp.Message) bool {
|
|
return msg.Type == ipv4.ICMPTypeEchoReply || msg.Type == ipv6.ICMPTypeEchoReply
|
|
}
|
|
|
|
func observeICMPRequest(logger *zerolog.Logger, span trace.Span, src string, dst string, echoID int, seq int) {
|
|
incrementICMPRequest()
|
|
logger.Debug().
|
|
Str("src", src).
|
|
Str("dst", dst).
|
|
Int("originalEchoID", echoID).
|
|
Int("originalEchoSeq", seq).
|
|
Msg("Received ICMP request")
|
|
span.SetAttributes(
|
|
attribute.Int("originalEchoID", echoID),
|
|
attribute.Int("seq", seq),
|
|
)
|
|
}
|
|
|
|
func observeICMPReply(logger *zerolog.Logger, span trace.Span, dst string, echoID int, seq int) {
|
|
incrementICMPReply()
|
|
logger.Debug().Str("dst", dst).Int("echoID", echoID).Int("seq", seq).Msg("Sent ICMP reply to edge")
|
|
span.SetAttributes(
|
|
attribute.String("dst", dst),
|
|
attribute.Int("echoID", echoID),
|
|
attribute.Int("seq", seq),
|
|
)
|
|
}
|