icmp_posix.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. //go:build darwin || linux
  2. package ingress
  3. // This file extracts logic shared by Linux and Darwin implementation if ICMPProxy.
  4. import (
  5. "fmt"
  6. "net"
  7. "net/netip"
  8. "sync/atomic"
  9. "github.com/google/gopacket/layers"
  10. "github.com/rs/zerolog"
  11. "golang.org/x/net/icmp"
  12. "github.com/cloudflare/cloudflared/packet"
  13. )
  14. // Opens a non-privileged ICMP socket on Linux and Darwin
  15. func newICMPConn(listenIP netip.Addr) (*icmp.PacketConn, error) {
  16. if listenIP.Is4() {
  17. return icmp.ListenPacket("udp4", listenIP.String())
  18. }
  19. return icmp.ListenPacket("udp6", listenIP.String())
  20. }
  21. func netipAddr(addr net.Addr) (netip.Addr, bool) {
  22. udpAddr, ok := addr.(*net.UDPAddr)
  23. if !ok {
  24. return netip.Addr{}, false
  25. }
  26. return udpAddr.AddrPort().Addr(), true
  27. }
  28. type flow3Tuple struct {
  29. srcIP netip.Addr
  30. dstIP netip.Addr
  31. originalEchoID int
  32. }
  33. // icmpEchoFlow implements the packet.Funnel interface.
  34. type icmpEchoFlow struct {
  35. *packet.ActivityTracker
  36. closeCallback func() error
  37. closed *atomic.Bool
  38. src netip.Addr
  39. originConn *icmp.PacketConn
  40. responder ICMPResponder
  41. assignedEchoID int
  42. originalEchoID int
  43. }
  44. func newICMPEchoFlow(src netip.Addr, closeCallback func() error, originConn *icmp.PacketConn, responder ICMPResponder, assignedEchoID, originalEchoID int) *icmpEchoFlow {
  45. return &icmpEchoFlow{
  46. ActivityTracker: packet.NewActivityTracker(),
  47. closeCallback: closeCallback,
  48. closed: &atomic.Bool{},
  49. src: src,
  50. originConn: originConn,
  51. responder: responder,
  52. assignedEchoID: assignedEchoID,
  53. originalEchoID: originalEchoID,
  54. }
  55. }
  56. func (ief *icmpEchoFlow) Equal(other packet.Funnel) bool {
  57. otherICMPFlow, ok := other.(*icmpEchoFlow)
  58. if !ok {
  59. return false
  60. }
  61. if otherICMPFlow.src != ief.src {
  62. return false
  63. }
  64. if otherICMPFlow.originalEchoID != ief.originalEchoID {
  65. return false
  66. }
  67. if otherICMPFlow.assignedEchoID != ief.assignedEchoID {
  68. return false
  69. }
  70. return true
  71. }
  72. func (ief *icmpEchoFlow) Close() error {
  73. ief.closed.Store(true)
  74. return ief.closeCallback()
  75. }
  76. func (ief *icmpEchoFlow) IsClosed() bool {
  77. return ief.closed.Load()
  78. }
  79. // sendToDst rewrites the echo ID to the one assigned to this flow
  80. func (ief *icmpEchoFlow) sendToDst(dst netip.Addr, msg *icmp.Message) error {
  81. ief.UpdateLastActive()
  82. originalEcho, err := getICMPEcho(msg)
  83. if err != nil {
  84. return err
  85. }
  86. sendMsg := icmp.Message{
  87. Type: msg.Type,
  88. Code: msg.Code,
  89. Body: &icmp.Echo{
  90. ID: ief.assignedEchoID,
  91. Seq: originalEcho.Seq,
  92. Data: originalEcho.Data,
  93. },
  94. }
  95. // For IPv4, the pseudoHeader is not used because the checksum is always calculated
  96. var pseudoHeader []byte = nil
  97. serializedPacket, err := sendMsg.Marshal(pseudoHeader)
  98. if err != nil {
  99. return err
  100. }
  101. _, err = ief.originConn.WriteTo(serializedPacket, &net.UDPAddr{
  102. IP: dst.AsSlice(),
  103. })
  104. return err
  105. }
  106. // returnToSrc rewrites the echo ID to the original echo ID from the eyeball
  107. func (ief *icmpEchoFlow) returnToSrc(reply *echoReply) error {
  108. ief.UpdateLastActive()
  109. reply.echo.ID = ief.originalEchoID
  110. reply.msg.Body = reply.echo
  111. pk := packet.ICMP{
  112. IP: &packet.IP{
  113. Src: reply.from,
  114. Dst: ief.src,
  115. Protocol: layers.IPProtocol(reply.msg.Type.Protocol()),
  116. TTL: packet.DefaultTTL,
  117. },
  118. Message: reply.msg,
  119. }
  120. return ief.responder.ReturnPacket(&pk)
  121. }
  122. type echoReply struct {
  123. from netip.Addr
  124. msg *icmp.Message
  125. echo *icmp.Echo
  126. }
  127. func parseReply(from net.Addr, rawMsg []byte) (*echoReply, error) {
  128. fromAddr, ok := netipAddr(from)
  129. if !ok {
  130. return nil, fmt.Errorf("cannot convert %s to netip.Addr", from)
  131. }
  132. proto := layers.IPProtocolICMPv4
  133. if fromAddr.Is6() {
  134. proto = layers.IPProtocolICMPv6
  135. }
  136. msg, err := icmp.ParseMessage(int(proto), rawMsg)
  137. if err != nil {
  138. return nil, err
  139. }
  140. echo, err := getICMPEcho(msg)
  141. if err != nil {
  142. return nil, err
  143. }
  144. return &echoReply{
  145. from: fromAddr,
  146. msg: msg,
  147. echo: echo,
  148. }, nil
  149. }
  150. func toICMPEchoFlow(funnel packet.Funnel) (*icmpEchoFlow, error) {
  151. icmpFlow, ok := funnel.(*icmpEchoFlow)
  152. if !ok {
  153. return nil, fmt.Errorf("%v is not *ICMPEchoFunnel", funnel)
  154. }
  155. return icmpFlow, nil
  156. }
  157. func createShouldReplaceFunnelFunc(logger *zerolog.Logger, responder ICMPResponder, pk *packet.ICMP, originalEchoID int) func(packet.Funnel) bool {
  158. return func(existing packet.Funnel) bool {
  159. existingFlow, err := toICMPEchoFlow(existing)
  160. if err != nil {
  161. logger.Err(err).
  162. Str("src", pk.Src.String()).
  163. Str("dst", pk.Dst.String()).
  164. Int("originalEchoID", originalEchoID).
  165. Msg("Funnel of wrong type found")
  166. return true
  167. }
  168. // Each quic connection should have a unique muxer.
  169. // If the existing flow has a different muxer, there's a new quic connection where return packets should be
  170. // routed. Otherwise, return packets will be send to the first observed incoming connection, rather than the
  171. // most recently observed connection.
  172. if existingFlow.responder.ConnectionIndex() != responder.ConnectionIndex() {
  173. logger.Debug().
  174. Str("src", pk.Src.String()).
  175. Str("dst", pk.Dst.String()).
  176. Int("originalEchoID", originalEchoID).
  177. Msg("Replacing funnel with new responder")
  178. return true
  179. }
  180. return false
  181. }
  182. }