152 lines
3.4 KiB
Go
152 lines
3.4 KiB
Go
package ingress
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/netip"
|
|
"runtime"
|
|
"testing"
|
|
|
|
"github.com/google/gopacket/layers"
|
|
"github.com/rs/zerolog"
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/net/icmp"
|
|
"golang.org/x/net/ipv4"
|
|
|
|
"github.com/cloudflare/cloudflared/packet"
|
|
)
|
|
|
|
var (
|
|
noopLogger = zerolog.Nop()
|
|
localhostIP = netip.MustParseAddr("127.0.0.1")
|
|
)
|
|
|
|
// TestICMPProxyEcho makes sure we can send ICMP echo via the Request method and receives response via the
|
|
// ListenResponse method
|
|
func TestICMPProxyEcho(t *testing.T) {
|
|
skipNonDarwin(t)
|
|
const (
|
|
echoID = 36571
|
|
endSeq = 100
|
|
)
|
|
|
|
proxy, err := NewICMPProxy(localhostIP.AsSlice(), &noopLogger)
|
|
require.NoError(t, err)
|
|
|
|
proxyDone := make(chan struct{})
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
go func() {
|
|
proxy.ListenResponse(ctx)
|
|
close(proxyDone)
|
|
}()
|
|
|
|
responder := echoFlowResponder{
|
|
decoder: packet.NewICMPDecoder(),
|
|
respChan: make(chan []byte),
|
|
}
|
|
|
|
ip := packet.IP{
|
|
Src: localhostIP,
|
|
Dst: localhostIP,
|
|
Protocol: layers.IPProtocolICMPv4,
|
|
}
|
|
for i := 0; i < endSeq; i++ {
|
|
pk := packet.ICMP{
|
|
IP: &ip,
|
|
Message: &icmp.Message{
|
|
Type: ipv4.ICMPTypeEcho,
|
|
Code: 0,
|
|
Body: &icmp.Echo{
|
|
ID: echoID,
|
|
Seq: i,
|
|
Data: []byte(fmt.Sprintf("icmp echo seq %d", i)),
|
|
},
|
|
},
|
|
}
|
|
require.NoError(t, proxy.Request(&pk, &responder))
|
|
responder.validate(t, &pk)
|
|
}
|
|
cancel()
|
|
<-proxyDone
|
|
}
|
|
|
|
// TestICMPProxyRejectNotEcho makes sure it rejects messages other than echo
|
|
func TestICMPProxyRejectNotEcho(t *testing.T) {
|
|
skipNonDarwin(t)
|
|
msgs := []icmp.Message{
|
|
{
|
|
Type: ipv4.ICMPTypeDestinationUnreachable,
|
|
Code: 1,
|
|
Body: &icmp.DstUnreach{
|
|
Data: []byte("original packet"),
|
|
},
|
|
},
|
|
{
|
|
Type: ipv4.ICMPTypeTimeExceeded,
|
|
Code: 1,
|
|
Body: &icmp.TimeExceeded{
|
|
Data: []byte("original packet"),
|
|
},
|
|
},
|
|
{
|
|
Type: ipv4.ICMPType(2),
|
|
Code: 0,
|
|
Body: &icmp.PacketTooBig{
|
|
MTU: 1280,
|
|
Data: []byte("original packet"),
|
|
},
|
|
},
|
|
}
|
|
proxy, err := NewICMPProxy(localhostIP.AsSlice(), &noopLogger)
|
|
require.NoError(t, err)
|
|
|
|
responder := echoFlowResponder{
|
|
decoder: packet.NewICMPDecoder(),
|
|
respChan: make(chan []byte),
|
|
}
|
|
for _, m := range msgs {
|
|
pk := packet.ICMP{
|
|
IP: &packet.IP{
|
|
Src: localhostIP,
|
|
Dst: localhostIP,
|
|
Protocol: layers.IPProtocolICMPv4,
|
|
},
|
|
Message: &m,
|
|
}
|
|
require.Error(t, proxy.Request(&pk, &responder))
|
|
}
|
|
}
|
|
|
|
func skipNonDarwin(t *testing.T) {
|
|
if runtime.GOOS != "darwin" {
|
|
t.Skip("Cannot create non-privileged datagram-oriented ICMP endpoint on Windows")
|
|
}
|
|
}
|
|
|
|
type echoFlowResponder struct {
|
|
decoder *packet.ICMPDecoder
|
|
respChan chan []byte
|
|
}
|
|
|
|
func (efr *echoFlowResponder) SendPacket(pk packet.RawPacket) error {
|
|
copiedPacket := make([]byte, len(pk.Data))
|
|
copy(copiedPacket, pk.Data)
|
|
efr.respChan <- copiedPacket
|
|
return nil
|
|
}
|
|
|
|
func (efr *echoFlowResponder) validate(t *testing.T, echoReq *packet.ICMP) {
|
|
pk := <-efr.respChan
|
|
decoded, err := efr.decoder.Decode(packet.RawPacket{Data: pk})
|
|
require.NoError(t, err)
|
|
require.Equal(t, decoded.Src, echoReq.Dst)
|
|
require.Equal(t, decoded.Dst, echoReq.Src)
|
|
require.Equal(t, echoReq.Protocol, decoded.Protocol)
|
|
|
|
require.Equal(t, ipv4.ICMPTypeEchoReply, decoded.Type)
|
|
require.Equal(t, 0, decoded.Code)
|
|
require.NotZero(t, decoded.Checksum)
|
|
// TODO: TUN-6586: Enable this validation when ICMP echo ID matches on Linux
|
|
require.Equal(t, echoReq.Body, decoded.Body)
|
|
}
|