mirror of
https://github.com/cloudflare/cloudflared.git
synced 2025-05-11 04:56:35 +00:00

Remove send and return methods from Funnel interface. Users of Funnel can provide their own send and return methods without wrapper to comply with the interface. Move packet router to ingress package to avoid circular dependency
177 lines
4.2 KiB
Go
177 lines
4.2 KiB
Go
//go:build darwin || linux
|
|
|
|
package ingress
|
|
|
|
// This file extracts logic shared by Linux and Darwin implementation if ICMPProxy.
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"net/netip"
|
|
|
|
"github.com/google/gopacket/layers"
|
|
"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, zone string) (*icmp.PacketConn, error) {
|
|
if listenIP.Is4() {
|
|
return icmp.ListenPacket("udp4", listenIP.String())
|
|
}
|
|
listenAddr := listenIP.String()
|
|
if zone != "" {
|
|
listenAddr = listenAddr + "%" + zone
|
|
}
|
|
return icmp.ListenPacket("udp6", listenAddr)
|
|
}
|
|
|
|
func netipAddr(addr net.Addr) (netip.Addr, bool) {
|
|
udpAddr, ok := addr.(*net.UDPAddr)
|
|
if !ok {
|
|
return netip.Addr{}, false
|
|
}
|
|
return netip.AddrFromSlice(udpAddr.IP)
|
|
}
|
|
|
|
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
|
|
src netip.Addr
|
|
originConn *icmp.PacketConn
|
|
responder *packetResponder
|
|
assignedEchoID int
|
|
originalEchoID int
|
|
// it's up to the user to ensure respEncoder is not used concurrently
|
|
respEncoder *packet.Encoder
|
|
}
|
|
|
|
func newICMPEchoFlow(src netip.Addr, closeCallback func() error, originConn *icmp.PacketConn, responder *packetResponder, assignedEchoID, originalEchoID int, respEncoder *packet.Encoder) *icmpEchoFlow {
|
|
return &icmpEchoFlow{
|
|
ActivityTracker: packet.NewActivityTracker(),
|
|
closeCallback: closeCallback,
|
|
src: src,
|
|
originConn: originConn,
|
|
responder: responder,
|
|
assignedEchoID: assignedEchoID,
|
|
originalEchoID: originalEchoID,
|
|
respEncoder: respEncoder,
|
|
}
|
|
}
|
|
|
|
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 {
|
|
return ief.closeCallback()
|
|
}
|
|
|
|
// 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,
|
|
}
|
|
serializedPacket, err := ief.respEncoder.Encode(&pk)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return ief.responder.returnPacket(serializedPacket)
|
|
}
|
|
|
|
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
|
|
}
|