123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- //go:build darwin || linux
- package ingress
- // This file extracts logic shared by Linux and Darwin implementation if ICMPProxy.
- import (
- "fmt"
- "net"
- "net/netip"
- "sync/atomic"
- "github.com/google/gopacket/layers"
- "github.com/rs/zerolog"
- "golang.org/x/net/icmp"
- "github.com/cloudflare/cloudflared/packet"
- )
- // Opens a non-privileged ICMP socket on Linux and Darwin
- func newICMPConn(listenIP netip.Addr) (*icmp.PacketConn, error) {
- if listenIP.Is4() {
- return icmp.ListenPacket("udp4", listenIP.String())
- }
- return icmp.ListenPacket("udp6", listenIP.String())
- }
- func netipAddr(addr net.Addr) (netip.Addr, bool) {
- udpAddr, ok := addr.(*net.UDPAddr)
- if !ok {
- return netip.Addr{}, false
- }
- return udpAddr.AddrPort().Addr(), true
- }
- type flow3Tuple struct {
- srcIP netip.Addr
- dstIP netip.Addr
- originalEchoID int
- }
- // icmpEchoFlow implements the packet.Funnel interface.
- type icmpEchoFlow struct {
- *packet.ActivityTracker
- closeCallback func() error
- closed *atomic.Bool
- src netip.Addr
- originConn *icmp.PacketConn
- responder ICMPResponder
- assignedEchoID int
- originalEchoID int
- }
- func newICMPEchoFlow(src netip.Addr, closeCallback func() error, originConn *icmp.PacketConn, responder ICMPResponder, assignedEchoID, originalEchoID int) *icmpEchoFlow {
- return &icmpEchoFlow{
- ActivityTracker: packet.NewActivityTracker(),
- closeCallback: closeCallback,
- closed: &atomic.Bool{},
- src: src,
- originConn: originConn,
- responder: responder,
- assignedEchoID: assignedEchoID,
- originalEchoID: originalEchoID,
- }
- }
- func (ief *icmpEchoFlow) Equal(other packet.Funnel) bool {
- otherICMPFlow, ok := other.(*icmpEchoFlow)
- if !ok {
- return false
- }
- if otherICMPFlow.src != ief.src {
- return false
- }
- if otherICMPFlow.originalEchoID != ief.originalEchoID {
- return false
- }
- if otherICMPFlow.assignedEchoID != ief.assignedEchoID {
- return false
- }
- return true
- }
- func (ief *icmpEchoFlow) Close() error {
- ief.closed.Store(true)
- return ief.closeCallback()
- }
- func (ief *icmpEchoFlow) IsClosed() bool {
- return ief.closed.Load()
- }
- // sendToDst rewrites the echo ID to the one assigned to this flow
- func (ief *icmpEchoFlow) sendToDst(dst netip.Addr, msg *icmp.Message) error {
- ief.UpdateLastActive()
- originalEcho, err := getICMPEcho(msg)
- if err != nil {
- return err
- }
- sendMsg := icmp.Message{
- Type: msg.Type,
- Code: msg.Code,
- Body: &icmp.Echo{
- ID: ief.assignedEchoID,
- Seq: originalEcho.Seq,
- Data: originalEcho.Data,
- },
- }
- // For IPv4, the pseudoHeader is not used because the checksum is always calculated
- var pseudoHeader []byte = nil
- serializedPacket, err := sendMsg.Marshal(pseudoHeader)
- if err != nil {
- return err
- }
- _, err = ief.originConn.WriteTo(serializedPacket, &net.UDPAddr{
- IP: dst.AsSlice(),
- })
- return err
- }
- // returnToSrc rewrites the echo ID to the original echo ID from the eyeball
- func (ief *icmpEchoFlow) returnToSrc(reply *echoReply) error {
- ief.UpdateLastActive()
- reply.echo.ID = ief.originalEchoID
- reply.msg.Body = reply.echo
- pk := packet.ICMP{
- IP: &packet.IP{
- Src: reply.from,
- Dst: ief.src,
- Protocol: layers.IPProtocol(reply.msg.Type.Protocol()),
- TTL: packet.DefaultTTL,
- },
- Message: reply.msg,
- }
- return ief.responder.ReturnPacket(&pk)
- }
- type echoReply struct {
- from netip.Addr
- msg *icmp.Message
- echo *icmp.Echo
- }
- func parseReply(from net.Addr, rawMsg []byte) (*echoReply, error) {
- fromAddr, ok := netipAddr(from)
- if !ok {
- return nil, fmt.Errorf("cannot convert %s to netip.Addr", from)
- }
- proto := layers.IPProtocolICMPv4
- if fromAddr.Is6() {
- proto = layers.IPProtocolICMPv6
- }
- msg, err := icmp.ParseMessage(int(proto), rawMsg)
- if err != nil {
- return nil, err
- }
- echo, err := getICMPEcho(msg)
- if err != nil {
- return nil, err
- }
- return &echoReply{
- from: fromAddr,
- msg: msg,
- echo: echo,
- }, nil
- }
- func toICMPEchoFlow(funnel packet.Funnel) (*icmpEchoFlow, error) {
- icmpFlow, ok := funnel.(*icmpEchoFlow)
- if !ok {
- return nil, fmt.Errorf("%v is not *ICMPEchoFunnel", funnel)
- }
- return icmpFlow, nil
- }
- func createShouldReplaceFunnelFunc(logger *zerolog.Logger, responder ICMPResponder, pk *packet.ICMP, originalEchoID int) func(packet.Funnel) bool {
- return func(existing packet.Funnel) bool {
- existingFlow, err := toICMPEchoFlow(existing)
- if err != nil {
- logger.Err(err).
- Str("src", pk.Src.String()).
- Str("dst", pk.Dst.String()).
- Int("originalEchoID", originalEchoID).
- Msg("Funnel of wrong type found")
- return true
- }
- // Each quic connection should have a unique muxer.
- // If the existing flow has a different muxer, there's a new quic connection where return packets should be
- // routed. Otherwise, return packets will be send to the first observed incoming connection, rather than the
- // most recently observed connection.
- if existingFlow.responder.ConnectionIndex() != responder.ConnectionIndex() {
- logger.Debug().
- Str("src", pk.Src.String()).
- Str("dst", pk.Dst.String()).
- Int("originalEchoID", originalEchoID).
- Msg("Replacing funnel with new responder")
- return true
- }
- return false
- }
- }
|