235 lines
5.2 KiB
Go
235 lines
5.2 KiB
Go
//go:build windows
|
|
|
|
package ingress
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"net/netip"
|
|
"testing"
|
|
"time"
|
|
"unsafe"
|
|
|
|
"golang.org/x/net/icmp"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestParseEchoReply tests parsing raw bytes from icmpSendEcho into echoResp
|
|
func TestParseEchoReply(t *testing.T) {
|
|
dst, err := inAddrV4(netip.MustParseAddr("192.168.10.20"))
|
|
require.NoError(t, err)
|
|
|
|
validReplyData := []byte(t.Name())
|
|
validReply := echoReply{
|
|
Address: dst,
|
|
Status: success,
|
|
RoundTripTime: uint32(20),
|
|
DataSize: uint16(len(validReplyData)),
|
|
DataPointer: &validReplyData[0],
|
|
Options: ipOption{
|
|
TTL: 59,
|
|
},
|
|
}
|
|
|
|
destHostUnreachableReply := validReply
|
|
destHostUnreachableReply.Status = destHostUnreachable
|
|
|
|
tests := []struct {
|
|
testCase string
|
|
replyBuf []byte
|
|
expectedReply *echoReply
|
|
expectedData []byte
|
|
}{
|
|
{
|
|
testCase: "empty buffer",
|
|
},
|
|
{
|
|
testCase: "status not success",
|
|
replyBuf: destHostUnreachableReply.marshal(t, []byte{}),
|
|
},
|
|
{
|
|
testCase: "valid reply",
|
|
replyBuf: validReply.marshal(t, validReplyData),
|
|
expectedReply: &validReply,
|
|
expectedData: validReplyData,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
resp, err := newEchoV4Resp(test.replyBuf)
|
|
if test.expectedReply == nil {
|
|
require.Error(t, err)
|
|
require.Nil(t, resp)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, resp.reply, test.expectedReply)
|
|
require.True(t, bytes.Equal(resp.data, test.expectedData))
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestParseEchoV6Reply tests parsing raw bytes from icmp6SendEcho into echoV6Resp
|
|
func TestParseEchoV6Reply(t *testing.T) {
|
|
dst := netip.MustParseAddr("2606:3600:4500::3333").As16()
|
|
var addr [8]uint16
|
|
for i := 0; i < 8; i++ {
|
|
addr[i] = binary.BigEndian.Uint16(dst[i*2 : i*2+2])
|
|
}
|
|
|
|
validReplyData := []byte(t.Name())
|
|
validReply := echoV6Reply{
|
|
Address: ipv6AddrEx{
|
|
addr: addr,
|
|
},
|
|
Status: success,
|
|
RoundTripTime: 25,
|
|
}
|
|
|
|
destHostUnreachableReply := validReply
|
|
destHostUnreachableReply.Status = ipv6DestUnreachable
|
|
|
|
tests := []struct {
|
|
testCase string
|
|
replyBuf []byte
|
|
expectedReply *echoV6Reply
|
|
expectedData []byte
|
|
}{
|
|
{
|
|
testCase: "empty buffer",
|
|
},
|
|
{
|
|
testCase: "status not success",
|
|
replyBuf: destHostUnreachableReply.marshal(t, []byte{}),
|
|
},
|
|
{
|
|
testCase: "valid reply",
|
|
replyBuf: validReply.marshal(t, validReplyData),
|
|
expectedReply: &validReply,
|
|
expectedData: validReplyData,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
resp, err := newEchoV6Resp(test.replyBuf, len(test.expectedData))
|
|
if test.expectedReply == nil {
|
|
require.Error(t, err)
|
|
require.Nil(t, resp)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, resp.reply, test.expectedReply)
|
|
require.True(t, bytes.Equal(resp.data, test.expectedData))
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestSendEchoErrors makes sure icmpSendEcho handles error cases
|
|
func TestSendEchoErrors(t *testing.T) {
|
|
testSendEchoErrors(t, netip.IPv4Unspecified())
|
|
testSendEchoErrors(t, netip.IPv6Unspecified())
|
|
}
|
|
|
|
func testSendEchoErrors(t *testing.T, listenIP netip.Addr) {
|
|
proxy, err := newICMPProxy(listenIP, &noopLogger, time.Second)
|
|
require.NoError(t, err)
|
|
|
|
echo := icmp.Echo{
|
|
ID: 6193,
|
|
Seq: 25712,
|
|
Data: []byte(t.Name()),
|
|
}
|
|
documentIP := netip.MustParseAddr("192.0.2.200")
|
|
if listenIP.Is6() {
|
|
documentIP = netip.MustParseAddr("2001:db8::1")
|
|
}
|
|
resp, err := proxy.icmpEchoRoundtrip(documentIP, &echo)
|
|
require.Error(t, err)
|
|
require.Nil(t, resp)
|
|
}
|
|
|
|
func (er *echoReply) marshal(t *testing.T, data []byte) []byte {
|
|
buf := new(bytes.Buffer)
|
|
|
|
for _, field := range []any{
|
|
er.Address,
|
|
er.Status,
|
|
er.RoundTripTime,
|
|
er.DataSize,
|
|
er.Reserved,
|
|
} {
|
|
require.NoError(t, binary.Write(buf, endian, field))
|
|
}
|
|
|
|
require.NoError(t, marshalPointer(buf, uintptr(unsafe.Pointer(er.DataPointer))))
|
|
|
|
for _, field := range []any{
|
|
er.Options.TTL,
|
|
er.Options.Tos,
|
|
er.Options.Flags,
|
|
er.Options.OptionsSize,
|
|
} {
|
|
require.NoError(t, binary.Write(buf, endian, field))
|
|
}
|
|
|
|
require.NoError(t, marshalPointer(buf, er.Options.OptionsData))
|
|
|
|
padSize := buf.Len() % int(unsafe.Alignof(er))
|
|
padding := make([]byte, padSize)
|
|
n, err := buf.Write(padding)
|
|
require.NoError(t, err)
|
|
require.Equal(t, padSize, n)
|
|
|
|
n, err = buf.Write(data)
|
|
require.NoError(t, err)
|
|
require.Equal(t, len(data), n)
|
|
|
|
return buf.Bytes()
|
|
}
|
|
|
|
func marshalPointer(buf io.Writer, ptr uintptr) error {
|
|
size := unsafe.Sizeof(ptr)
|
|
switch size {
|
|
case 4:
|
|
return binary.Write(buf, endian, uint32(ptr))
|
|
case 8:
|
|
return binary.Write(buf, endian, uint64(ptr))
|
|
default:
|
|
return fmt.Errorf("unexpected pointer size %d", size)
|
|
}
|
|
}
|
|
|
|
func (er *echoV6Reply) marshal(t *testing.T, data []byte) []byte {
|
|
buf := new(bytes.Buffer)
|
|
|
|
for _, field := range []any{
|
|
er.Address.port,
|
|
er.Address.flowInfoUpper,
|
|
er.Address.flowInfoLower,
|
|
er.Address.addr,
|
|
er.Address.scopeID,
|
|
} {
|
|
require.NoError(t, binary.Write(buf, endian, field))
|
|
}
|
|
|
|
padSize := buf.Len() % int(unsafe.Alignof(er))
|
|
padding := make([]byte, padSize)
|
|
n, err := buf.Write(padding)
|
|
require.NoError(t, err)
|
|
require.Equal(t, padSize, n)
|
|
|
|
for _, field := range []any{
|
|
er.Status,
|
|
er.RoundTripTime,
|
|
} {
|
|
require.NoError(t, binary.Write(buf, endian, field))
|
|
}
|
|
|
|
n, err = buf.Write(data)
|
|
require.NoError(t, err)
|
|
require.Equal(t, len(data), n)
|
|
|
|
return buf.Bytes()
|
|
}
|