summaryrefslogtreecommitdiffhomepage
path: root/test/packetimpact/testbench
diff options
context:
space:
mode:
Diffstat (limited to 'test/packetimpact/testbench')
-rw-r--r--test/packetimpact/testbench/BUILD11
-rw-r--r--test/packetimpact/testbench/connections.go287
-rw-r--r--test/packetimpact/testbench/dut.go362
-rw-r--r--test/packetimpact/testbench/layers.go284
-rw-r--r--test/packetimpact/testbench/layers_test.go144
-rw-r--r--test/packetimpact/testbench/rawsockets.go6
6 files changed, 863 insertions, 231 deletions
diff --git a/test/packetimpact/testbench/BUILD b/test/packetimpact/testbench/BUILD
index a34c81fcc..838a10ffe 100644
--- a/test/packetimpact/testbench/BUILD
+++ b/test/packetimpact/testbench/BUILD
@@ -1,4 +1,4 @@
-load("//tools:defs.bzl", "go_library")
+load("//tools:defs.bzl", "go_library", "go_test")
package(
default_visibility = ["//test/packetimpact:__subpackages__"],
@@ -16,6 +16,7 @@ go_library(
],
deps = [
"//pkg/tcpip",
+ "//pkg/tcpip/buffer",
"//pkg/tcpip/header",
"//pkg/tcpip/seqnum",
"//pkg/usermem",
@@ -29,3 +30,11 @@ go_library(
"@org_golang_x_sys//unix:go_default_library",
],
)
+
+go_test(
+ name = "testbench_test",
+ size = "small",
+ srcs = ["layers_test.go"],
+ library = ":testbench",
+ deps = ["//pkg/tcpip"],
+)
diff --git a/test/packetimpact/testbench/connections.go b/test/packetimpact/testbench/connections.go
index b7aa63934..79c0ccf5c 100644
--- a/test/packetimpact/testbench/connections.go
+++ b/test/packetimpact/testbench/connections.go
@@ -21,6 +21,7 @@ import (
"fmt"
"math/rand"
"net"
+ "strings"
"testing"
"time"
@@ -36,19 +37,6 @@ var remoteIPv4 = flag.String("remote_ipv4", "", "remote IPv4 address for test pa
var localMAC = flag.String("local_mac", "", "local mac address for test packets")
var remoteMAC = flag.String("remote_mac", "", "remote mac address for test packets")
-// TCPIPv4 maintains state about a TCP/IPv4 connection.
-type TCPIPv4 struct {
- outgoing Layers
- incoming Layers
- LocalSeqNum seqnum.Value
- RemoteSeqNum seqnum.Value
- SynAck *TCP
- sniffer Sniffer
- injector Injector
- portPickerFD int
- t *testing.T
-}
-
// pickPort makes a new socket and returns the socket FD and port. The caller
// must close the FD when done with the port if there is no error.
func pickPort() (int, uint16, error) {
@@ -75,12 +63,25 @@ func pickPort() (int, uint16, error) {
return fd, uint16(newSockAddrInet4.Port), nil
}
+// TCPIPv4 maintains state about a TCP/IPv4 connection.
+type TCPIPv4 struct {
+ outgoing Layers
+ incoming Layers
+ LocalSeqNum seqnum.Value
+ RemoteSeqNum seqnum.Value
+ SynAck *TCP
+ sniffer Sniffer
+ injector Injector
+ portPickerFD int
+ t *testing.T
+}
+
// tcpLayerIndex is the position of the TCP layer in the TCPIPv4 connection. It
// is the third, after Ethernet and IPv4.
const tcpLayerIndex int = 2
// NewTCPIPv4 creates a new TCPIPv4 connection with reasonable defaults.
-func NewTCPIPv4(t *testing.T, dut DUT, outgoingTCP, incomingTCP TCP) TCPIPv4 {
+func NewTCPIPv4(t *testing.T, outgoingTCP, incomingTCP TCP) TCPIPv4 {
lMAC, err := tcpip.ParseMACAddress(*localMAC)
if err != nil {
t.Fatalf("can't parse localMAC %q: %s", *localMAC, err)
@@ -109,18 +110,16 @@ func NewTCPIPv4(t *testing.T, dut DUT, outgoingTCP, incomingTCP TCP) TCPIPv4 {
}
newOutgoingTCP := &TCP{
- DataOffset: Uint8(header.TCPMinimumSize),
- WindowSize: Uint16(32768),
- SrcPort: &localPort,
+ SrcPort: &localPort,
}
if err := newOutgoingTCP.merge(outgoingTCP); err != nil {
- t.Fatalf("can't merge %v into %v: %s", outgoingTCP, newOutgoingTCP, err)
+ t.Fatalf("can't merge %+v into %+v: %s", outgoingTCP, newOutgoingTCP, err)
}
newIncomingTCP := &TCP{
DstPort: &localPort,
}
if err := newIncomingTCP.merge(incomingTCP); err != nil {
- t.Fatalf("can't merge %v into %v: %s", incomingTCP, newIncomingTCP, err)
+ t.Fatalf("can't merge %+v into %+v: %s", incomingTCP, newIncomingTCP, err)
}
return TCPIPv4{
outgoing: Layers{
@@ -149,8 +148,9 @@ func (conn *TCPIPv4) Close() {
conn.portPickerFD = -1
}
-// Send a packet with reasonable defaults and override some fields by tcp.
-func (conn *TCPIPv4) Send(tcp TCP, additionalLayers ...Layer) {
+// CreateFrame builds a frame for the connection with tcp overriding defaults
+// and additionalLayers added after the TCP header.
+func (conn *TCPIPv4) CreateFrame(tcp TCP, additionalLayers ...Layer) Layers {
if tcp.SeqNum == nil {
tcp.SeqNum = Uint32(uint32(conn.LocalSeqNum))
}
@@ -159,30 +159,51 @@ func (conn *TCPIPv4) Send(tcp TCP, additionalLayers ...Layer) {
}
layersToSend := deepcopy.Copy(conn.outgoing).(Layers)
if err := layersToSend[tcpLayerIndex].(*TCP).merge(tcp); err != nil {
- conn.t.Fatalf("can't merge %v into %v: %s", tcp, layersToSend[tcpLayerIndex], err)
+ conn.t.Fatalf("can't merge %+v into %+v: %s", tcp, layersToSend[tcpLayerIndex], err)
}
layersToSend = append(layersToSend, additionalLayers...)
- outBytes, err := layersToSend.toBytes()
+ return layersToSend
+}
+
+// SendFrame sends a frame with reasonable defaults.
+func (conn *TCPIPv4) SendFrame(frame Layers) {
+ outBytes, err := frame.toBytes()
if err != nil {
conn.t.Fatalf("can't build outgoing TCP packet: %s", err)
}
conn.injector.Send(outBytes)
// Compute the next TCP sequence number.
- for i := tcpLayerIndex + 1; i < len(layersToSend); i++ {
- conn.LocalSeqNum.UpdateForward(seqnum.Size(layersToSend[i].length()))
+ for i := tcpLayerIndex + 1; i < len(frame); i++ {
+ conn.LocalSeqNum.UpdateForward(seqnum.Size(frame[i].length()))
}
+ tcp := frame[tcpLayerIndex].(*TCP)
if tcp.Flags != nil && *tcp.Flags&(header.TCPFlagSyn|header.TCPFlagFin) != 0 {
conn.LocalSeqNum.UpdateForward(1)
}
}
-// Recv gets a packet from the sniffer within the timeout provided. If no packet
-// arrives before the timeout, it returns nil.
+// Send a packet with reasonable defaults and override some fields by tcp.
+func (conn *TCPIPv4) Send(tcp TCP, additionalLayers ...Layer) {
+ conn.SendFrame(conn.CreateFrame(tcp, additionalLayers...))
+}
+
+// Recv gets a packet from the sniffer within the timeout provided.
+// If no packet arrives before the timeout, it returns nil.
func (conn *TCPIPv4) Recv(timeout time.Duration) *TCP {
+ layers := conn.RecvFrame(timeout)
+ if tcpLayerIndex < len(layers) {
+ return layers[tcpLayerIndex].(*TCP)
+ }
+ return nil
+}
+
+// RecvFrame gets a frame (of type Layers) within the timeout provided.
+// If no frame arrives before the timeout, it returns nil.
+func (conn *TCPIPv4) RecvFrame(timeout time.Duration) Layers {
deadline := time.Now().Add(timeout)
for {
- timeout = deadline.Sub(time.Now())
+ timeout = time.Until(deadline)
if timeout <= 0 {
break
}
@@ -190,10 +211,7 @@ func (conn *TCPIPv4) Recv(timeout time.Duration) *TCP {
if b == nil {
break
}
- layers, err := ParseEther(b)
- if err != nil {
- continue // Ignore packets that can't be parsed.
- }
+ layers := Parse(ParseEther, b)
if !conn.incoming.match(layers) {
continue // Ignore packets that don't match the expected incoming.
}
@@ -205,41 +223,222 @@ func (conn *TCPIPv4) Recv(timeout time.Duration) *TCP {
for i := tcpLayerIndex + 1; i < len(layers); i++ {
conn.RemoteSeqNum.UpdateForward(seqnum.Size(layers[i].length()))
}
- return tcpHeader
+ return layers
}
return nil
}
// Expect a packet that matches the provided tcp within the timeout specified.
-// If it doesn't arrive in time, the test fails.
-func (conn *TCPIPv4) Expect(tcp TCP, timeout time.Duration) *TCP {
+// If it doesn't arrive in time, it returns nil.
+func (conn *TCPIPv4) Expect(tcp TCP, timeout time.Duration) (*TCP, error) {
+ // We cannot implement this directly using ExpectFrame as we cannot specify
+ // the Payload part.
deadline := time.Now().Add(timeout)
+ var allTCP []string
for {
- timeout = deadline.Sub(time.Now())
- if timeout <= 0 {
- return nil
+ var gotTCP *TCP
+ if timeout = time.Until(deadline); timeout > 0 {
+ gotTCP = conn.Recv(timeout)
}
- gotTCP := conn.Recv(timeout)
if gotTCP == nil {
- return nil
+ return nil, fmt.Errorf("got %d packets:\n%s", len(allTCP), strings.Join(allTCP, "\n"))
}
if tcp.match(gotTCP) {
- return gotTCP
+ return gotTCP, nil
+ }
+ allTCP = append(allTCP, gotTCP.String())
+ }
+}
+
+// ExpectFrame expects a frame that matches the specified layers within the
+// timeout specified. If it doesn't arrive in time, it returns nil.
+func (conn *TCPIPv4) ExpectFrame(layers Layers, timeout time.Duration) Layers {
+ deadline := time.Now().Add(timeout)
+ for {
+ timeout = time.Until(deadline)
+ if timeout <= 0 {
+ return nil
+ }
+ gotLayers := conn.RecvFrame(timeout)
+ if layers.match(gotLayers) {
+ return gotLayers
}
}
}
+// ExpectData is a convenient method that expects a TCP packet along with
+// the payload to arrive within the timeout specified. If it doesn't arrive
+// in time, it causes a fatal test failure.
+func (conn *TCPIPv4) ExpectData(tcp TCP, data []byte, timeout time.Duration) {
+ expected := []Layer{&Ether{}, &IPv4{}, &tcp}
+ if len(data) > 0 {
+ expected = append(expected, &Payload{Bytes: data})
+ }
+ if conn.ExpectFrame(expected, timeout) == nil {
+ conn.t.Fatalf("expected to get a TCP frame %s with payload %x", &tcp, data)
+ }
+}
+
// Handshake performs a TCP 3-way handshake.
func (conn *TCPIPv4) Handshake() {
// Send the SYN.
conn.Send(TCP{Flags: Uint8(header.TCPFlagSyn)})
// Wait for the SYN-ACK.
- conn.SynAck = conn.Expect(TCP{Flags: Uint8(header.TCPFlagSyn | header.TCPFlagAck)}, time.Second)
- if conn.SynAck == nil {
- conn.t.Fatalf("didn't get synack during handshake")
+ synAck, err := conn.Expect(TCP{Flags: Uint8(header.TCPFlagSyn | header.TCPFlagAck)}, time.Second)
+ if synAck == nil {
+ conn.t.Fatalf("didn't get synack during handshake: %s", err)
}
+ conn.SynAck = synAck
// Send an ACK.
conn.Send(TCP{Flags: Uint8(header.TCPFlagAck)})
}
+
+// UDPIPv4 maintains state about a UDP/IPv4 connection.
+type UDPIPv4 struct {
+ outgoing Layers
+ incoming Layers
+ sniffer Sniffer
+ injector Injector
+ portPickerFD int
+ t *testing.T
+}
+
+// udpLayerIndex is the position of the UDP layer in the UDPIPv4 connection. It
+// is the third, after Ethernet and IPv4.
+const udpLayerIndex int = 2
+
+// NewUDPIPv4 creates a new UDPIPv4 connection with reasonable defaults.
+func NewUDPIPv4(t *testing.T, outgoingUDP, incomingUDP UDP) UDPIPv4 {
+ lMAC, err := tcpip.ParseMACAddress(*localMAC)
+ if err != nil {
+ t.Fatalf("can't parse localMAC %q: %s", *localMAC, err)
+ }
+
+ rMAC, err := tcpip.ParseMACAddress(*remoteMAC)
+ if err != nil {
+ t.Fatalf("can't parse remoteMAC %q: %s", *remoteMAC, err)
+ }
+
+ portPickerFD, localPort, err := pickPort()
+ if err != nil {
+ t.Fatalf("can't pick a port: %s", err)
+ }
+ lIP := tcpip.Address(net.ParseIP(*localIPv4).To4())
+ rIP := tcpip.Address(net.ParseIP(*remoteIPv4).To4())
+
+ sniffer, err := NewSniffer(t)
+ if err != nil {
+ t.Fatalf("can't make new sniffer: %s", err)
+ }
+
+ injector, err := NewInjector(t)
+ if err != nil {
+ t.Fatalf("can't make new injector: %s", err)
+ }
+
+ newOutgoingUDP := &UDP{
+ SrcPort: &localPort,
+ }
+ if err := newOutgoingUDP.merge(outgoingUDP); err != nil {
+ t.Fatalf("can't merge %+v into %+v: %s", outgoingUDP, newOutgoingUDP, err)
+ }
+ newIncomingUDP := &UDP{
+ DstPort: &localPort,
+ }
+ if err := newIncomingUDP.merge(incomingUDP); err != nil {
+ t.Fatalf("can't merge %+v into %+v: %s", incomingUDP, newIncomingUDP, err)
+ }
+ return UDPIPv4{
+ outgoing: Layers{
+ &Ether{SrcAddr: &lMAC, DstAddr: &rMAC},
+ &IPv4{SrcAddr: &lIP, DstAddr: &rIP},
+ newOutgoingUDP},
+ incoming: Layers{
+ &Ether{SrcAddr: &rMAC, DstAddr: &lMAC},
+ &IPv4{SrcAddr: &rIP, DstAddr: &lIP},
+ newIncomingUDP},
+ sniffer: sniffer,
+ injector: injector,
+ portPickerFD: portPickerFD,
+ t: t,
+ }
+}
+
+// Close the injector and sniffer associated with this connection.
+func (conn *UDPIPv4) Close() {
+ conn.sniffer.Close()
+ conn.injector.Close()
+ if err := unix.Close(conn.portPickerFD); err != nil {
+ conn.t.Fatalf("can't close portPickerFD: %s", err)
+ }
+ conn.portPickerFD = -1
+}
+
+// CreateFrame builds a frame for the connection with the provided udp
+// overriding defaults and the additionalLayers added after the UDP header.
+func (conn *UDPIPv4) CreateFrame(udp UDP, additionalLayers ...Layer) Layers {
+ layersToSend := deepcopy.Copy(conn.outgoing).(Layers)
+ if err := layersToSend[udpLayerIndex].(*UDP).merge(udp); err != nil {
+ conn.t.Fatalf("can't merge %+v into %+v: %s", udp, layersToSend[udpLayerIndex], err)
+ }
+ layersToSend = append(layersToSend, additionalLayers...)
+ return layersToSend
+}
+
+// SendFrame sends a frame with reasonable defaults.
+func (conn *UDPIPv4) SendFrame(frame Layers) {
+ outBytes, err := frame.toBytes()
+ if err != nil {
+ conn.t.Fatalf("can't build outgoing UDP packet: %s", err)
+ }
+ conn.injector.Send(outBytes)
+}
+
+// Send a packet with reasonable defaults and override some fields by udp.
+func (conn *UDPIPv4) Send(udp UDP, additionalLayers ...Layer) {
+ conn.SendFrame(conn.CreateFrame(udp, additionalLayers...))
+}
+
+// Recv gets a packet from the sniffer within the timeout provided. If no packet
+// arrives before the timeout, it returns nil.
+func (conn *UDPIPv4) Recv(timeout time.Duration) *UDP {
+ deadline := time.Now().Add(timeout)
+ for {
+ timeout = time.Until(deadline)
+ if timeout <= 0 {
+ break
+ }
+ b := conn.sniffer.Recv(timeout)
+ if b == nil {
+ break
+ }
+ layers := Parse(ParseEther, b)
+ if !conn.incoming.match(layers) {
+ continue // Ignore packets that don't match the expected incoming.
+ }
+ return (layers[udpLayerIndex]).(*UDP)
+ }
+ return nil
+}
+
+// Expect a packet that matches the provided udp within the timeout specified.
+// If it doesn't arrive in time, the test fails.
+func (conn *UDPIPv4) Expect(udp UDP, timeout time.Duration) (*UDP, error) {
+ deadline := time.Now().Add(timeout)
+ var allUDP []string
+ for {
+ var gotUDP *UDP
+ if timeout = time.Until(deadline); timeout > 0 {
+ gotUDP = conn.Recv(timeout)
+ }
+ if gotUDP == nil {
+ return nil, fmt.Errorf("got %d packets:\n%s", len(allUDP), strings.Join(allUDP, "\n"))
+ }
+ if udp.match(gotUDP) {
+ return gotUDP, nil
+ }
+ allUDP = append(allUDP, gotUDP.String())
+ }
+}
diff --git a/test/packetimpact/testbench/dut.go b/test/packetimpact/testbench/dut.go
index 8ea1706d3..9335909c0 100644
--- a/test/packetimpact/testbench/dut.go
+++ b/test/packetimpact/testbench/dut.go
@@ -65,33 +65,6 @@ func (dut *DUT) TearDown() {
dut.conn.Close()
}
-// SocketWithErrno calls socket on the DUT and returns the fd and errno.
-func (dut *DUT) SocketWithErrno(domain, typ, proto int32) (int32, error) {
- dut.t.Helper()
- req := pb.SocketRequest{
- Domain: domain,
- Type: typ,
- Protocol: proto,
- }
- ctx := context.Background()
- resp, err := dut.posixServer.Socket(ctx, &req)
- if err != nil {
- dut.t.Fatalf("failed to call Socket: %s", err)
- }
- return resp.GetFd(), syscall.Errno(resp.GetErrno_())
-}
-
-// Socket calls socket on the DUT and returns the file descriptor. If socket
-// fails on the DUT, the test ends.
-func (dut *DUT) Socket(domain, typ, proto int32) int32 {
- dut.t.Helper()
- fd, err := dut.SocketWithErrno(domain, typ, proto)
- if fd < 0 {
- dut.t.Fatalf("failed to create socket: %s", err)
- }
- return fd
-}
-
func (dut *DUT) sockaddrToProto(sa unix.Sockaddr) *pb.Sockaddr {
dut.t.Helper()
switch s := sa.(type) {
@@ -142,14 +115,95 @@ func (dut *DUT) protoToSockaddr(sa *pb.Sockaddr) unix.Sockaddr {
return nil
}
+// CreateBoundSocket makes a new socket on the DUT, with type typ and protocol
+// proto, and bound to the IP address addr. Returns the new file descriptor and
+// the port that was selected on the DUT.
+func (dut *DUT) CreateBoundSocket(typ, proto int32, addr net.IP) (int32, uint16) {
+ dut.t.Helper()
+ var fd int32
+ if addr.To4() != nil {
+ fd = dut.Socket(unix.AF_INET, typ, proto)
+ sa := unix.SockaddrInet4{}
+ copy(sa.Addr[:], addr.To4())
+ dut.Bind(fd, &sa)
+ } else if addr.To16() != nil {
+ fd = dut.Socket(unix.AF_INET6, typ, proto)
+ sa := unix.SockaddrInet6{}
+ copy(sa.Addr[:], addr.To16())
+ dut.Bind(fd, &sa)
+ } else {
+ dut.t.Fatal("unknown ip addr type for remoteIP")
+ }
+ sa := dut.GetSockName(fd)
+ var port int
+ switch s := sa.(type) {
+ case *unix.SockaddrInet4:
+ port = s.Port
+ case *unix.SockaddrInet6:
+ port = s.Port
+ default:
+ dut.t.Fatalf("unknown sockaddr type from getsockname: %t", sa)
+ }
+ return fd, uint16(port)
+}
+
+// CreateListener makes a new TCP connection. If it fails, the test ends.
+func (dut *DUT) CreateListener(typ, proto, backlog int32) (int32, uint16) {
+ fd, remotePort := dut.CreateBoundSocket(typ, proto, net.ParseIP(*remoteIPv4))
+ dut.Listen(fd, backlog)
+ return fd, remotePort
+}
+
+// All the functions that make gRPC calls to the Posix service are below, sorted
+// alphabetically.
+
+// Accept calls accept on the DUT and causes a fatal test failure if it doesn't
+// succeed. If more control over the timeout or error handling is needed, use
+// AcceptWithErrno.
+func (dut *DUT) Accept(sockfd int32) (int32, unix.Sockaddr) {
+ dut.t.Helper()
+ ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout)
+ defer cancel()
+ fd, sa, err := dut.AcceptWithErrno(ctx, sockfd)
+ if fd < 0 {
+ dut.t.Fatalf("failed to accept: %s", err)
+ }
+ return fd, sa
+}
+
+// AcceptWithErrno calls accept on the DUT.
+func (dut *DUT) AcceptWithErrno(ctx context.Context, sockfd int32) (int32, unix.Sockaddr, error) {
+ dut.t.Helper()
+ req := pb.AcceptRequest{
+ Sockfd: sockfd,
+ }
+ resp, err := dut.posixServer.Accept(ctx, &req)
+ if err != nil {
+ dut.t.Fatalf("failed to call Accept: %s", err)
+ }
+ return resp.GetFd(), dut.protoToSockaddr(resp.GetAddr()), syscall.Errno(resp.GetErrno_())
+}
+
+// Bind calls bind on the DUT and causes a fatal test failure if it doesn't
+// succeed. If more control over the timeout or error handling is
+// needed, use BindWithErrno.
+func (dut *DUT) Bind(fd int32, sa unix.Sockaddr) {
+ dut.t.Helper()
+ ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout)
+ defer cancel()
+ ret, err := dut.BindWithErrno(ctx, fd, sa)
+ if ret != 0 {
+ dut.t.Fatalf("failed to bind socket: %s", err)
+ }
+}
+
// BindWithErrno calls bind on the DUT.
-func (dut *DUT) BindWithErrno(fd int32, sa unix.Sockaddr) (int32, error) {
+func (dut *DUT) BindWithErrno(ctx context.Context, fd int32, sa unix.Sockaddr) (int32, error) {
dut.t.Helper()
req := pb.BindRequest{
Sockfd: fd,
Addr: dut.sockaddrToProto(sa),
}
- ctx := context.Background()
resp, err := dut.posixServer.Bind(ctx, &req)
if err != nil {
dut.t.Fatalf("failed to call Bind: %s", err)
@@ -157,23 +211,52 @@ func (dut *DUT) BindWithErrno(fd int32, sa unix.Sockaddr) (int32, error) {
return resp.GetRet(), syscall.Errno(resp.GetErrno_())
}
-// Bind calls bind on the DUT and causes a fatal test failure if it doesn't
-// succeed.
-func (dut *DUT) Bind(fd int32, sa unix.Sockaddr) {
+// Close calls close on the DUT and causes a fatal test failure if it doesn't
+// succeed. If more control over the timeout or error handling is needed, use
+// CloseWithErrno.
+func (dut *DUT) Close(fd int32) {
dut.t.Helper()
- ret, err := dut.BindWithErrno(fd, sa)
+ ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout)
+ defer cancel()
+ ret, err := dut.CloseWithErrno(ctx, fd)
if ret != 0 {
- dut.t.Fatalf("failed to bind socket: %s", err)
+ dut.t.Fatalf("failed to close: %s", err)
}
}
+// CloseWithErrno calls close on the DUT.
+func (dut *DUT) CloseWithErrno(ctx context.Context, fd int32) (int32, error) {
+ dut.t.Helper()
+ req := pb.CloseRequest{
+ Fd: fd,
+ }
+ resp, err := dut.posixServer.Close(ctx, &req)
+ if err != nil {
+ dut.t.Fatalf("failed to call Close: %s", err)
+ }
+ return resp.GetRet(), syscall.Errno(resp.GetErrno_())
+}
+
+// GetSockName calls getsockname on the DUT and causes a fatal test failure if
+// it doesn't succeed. If more control over the timeout or error handling is
+// needed, use GetSockNameWithErrno.
+func (dut *DUT) GetSockName(sockfd int32) unix.Sockaddr {
+ dut.t.Helper()
+ ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout)
+ defer cancel()
+ ret, sa, err := dut.GetSockNameWithErrno(ctx, sockfd)
+ if ret != 0 {
+ dut.t.Fatalf("failed to getsockname: %s", err)
+ }
+ return sa
+}
+
// GetSockNameWithErrno calls getsockname on the DUT.
-func (dut *DUT) GetSockNameWithErrno(sockfd int32) (int32, unix.Sockaddr, error) {
+func (dut *DUT) GetSockNameWithErrno(ctx context.Context, sockfd int32) (int32, unix.Sockaddr, error) {
dut.t.Helper()
req := pb.GetSockNameRequest{
Sockfd: sockfd,
}
- ctx := context.Background()
resp, err := dut.posixServer.GetSockName(ctx, &req)
if err != nil {
dut.t.Fatalf("failed to call Bind: %s", err)
@@ -181,26 +264,26 @@ func (dut *DUT) GetSockNameWithErrno(sockfd int32) (int32, unix.Sockaddr, error)
return resp.GetRet(), dut.protoToSockaddr(resp.GetAddr()), syscall.Errno(resp.GetErrno_())
}
-// GetSockName calls getsockname on the DUT and causes a fatal test failure if
-// it doens't succeed.
-func (dut *DUT) GetSockName(sockfd int32) unix.Sockaddr {
+// Listen calls listen on the DUT and causes a fatal test failure if it doesn't
+// succeed. If more control over the timeout or error handling is needed, use
+// ListenWithErrno.
+func (dut *DUT) Listen(sockfd, backlog int32) {
dut.t.Helper()
- ret, sa, err := dut.GetSockNameWithErrno(sockfd)
+ ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout)
+ defer cancel()
+ ret, err := dut.ListenWithErrno(ctx, sockfd, backlog)
if ret != 0 {
- dut.t.Fatalf("failed to getsockname: %s", err)
+ dut.t.Fatalf("failed to listen: %s", err)
}
- return sa
}
// ListenWithErrno calls listen on the DUT.
-func (dut *DUT) ListenWithErrno(sockfd, backlog int32) (int32, error) {
+func (dut *DUT) ListenWithErrno(ctx context.Context, sockfd, backlog int32) (int32, error) {
dut.t.Helper()
req := pb.ListenRequest{
Sockfd: sockfd,
Backlog: backlog,
}
- ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout)
- defer cancel()
resp, err := dut.posixServer.Listen(ctx, &req)
if err != nil {
dut.t.Fatalf("failed to call Listen: %s", err)
@@ -208,44 +291,54 @@ func (dut *DUT) ListenWithErrno(sockfd, backlog int32) (int32, error) {
return resp.GetRet(), syscall.Errno(resp.GetErrno_())
}
-// Listen calls listen on the DUT and causes a fatal test failure if it doesn't
-// succeed.
-func (dut *DUT) Listen(sockfd, backlog int32) {
+// Send calls send on the DUT and causes a fatal test failure if it doesn't
+// succeed. If more control over the timeout or error handling is needed, use
+// SendWithErrno.
+func (dut *DUT) Send(sockfd int32, buf []byte, flags int32) int32 {
dut.t.Helper()
- ret, err := dut.ListenWithErrno(sockfd, backlog)
- if ret != 0 {
- dut.t.Fatalf("failed to listen: %s", err)
+ ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout)
+ defer cancel()
+ ret, err := dut.SendWithErrno(ctx, sockfd, buf, flags)
+ if ret == -1 {
+ dut.t.Fatalf("failed to send: %s", err)
}
+ return ret
}
-// AcceptWithErrno calls accept on the DUT.
-func (dut *DUT) AcceptWithErrno(sockfd int32) (int32, unix.Sockaddr, error) {
+// SendWithErrno calls send on the DUT.
+func (dut *DUT) SendWithErrno(ctx context.Context, sockfd int32, buf []byte, flags int32) (int32, error) {
dut.t.Helper()
- req := pb.AcceptRequest{
+ req := pb.SendRequest{
Sockfd: sockfd,
+ Buf: buf,
+ Flags: flags,
}
- ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout)
- defer cancel()
- resp, err := dut.posixServer.Accept(ctx, &req)
+ resp, err := dut.posixServer.Send(ctx, &req)
if err != nil {
- dut.t.Fatalf("failed to call Accept: %s", err)
+ dut.t.Fatalf("failed to call Send: %s", err)
}
- return resp.GetFd(), dut.protoToSockaddr(resp.GetAddr()), syscall.Errno(resp.GetErrno_())
+ return resp.GetRet(), syscall.Errno(resp.GetErrno_())
}
-// Accept calls accept on the DUT and causes a fatal test failure if it doesn't
-// succeed.
-func (dut *DUT) Accept(sockfd int32) (int32, unix.Sockaddr) {
+// SetSockOpt calls setsockopt on the DUT and causes a fatal test failure if it
+// doesn't succeed. If more control over the timeout or error handling is
+// needed, use SetSockOptWithErrno. Because endianess and the width of values
+// might differ between the testbench and DUT architectures, prefer to use a
+// more specific SetSockOptXxx function.
+func (dut *DUT) SetSockOpt(sockfd, level, optname int32, optval []byte) {
dut.t.Helper()
- fd, sa, err := dut.AcceptWithErrno(sockfd)
- if fd < 0 {
- dut.t.Fatalf("failed to accept: %s", err)
+ ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout)
+ defer cancel()
+ ret, err := dut.SetSockOptWithErrno(ctx, sockfd, level, optname, optval)
+ if ret != 0 {
+ dut.t.Fatalf("failed to SetSockOpt: %s", err)
}
- return fd, sa
}
-// SetSockOptWithErrno calls setsockopt on the DUT.
-func (dut *DUT) SetSockOptWithErrno(sockfd, level, optname int32, optval []byte) (int32, error) {
+// SetSockOptWithErrno calls setsockopt on the DUT. Because endianess and the
+// width of values might differ between the testbench and DUT architectures,
+// prefer to use a more specific SetSockOptXxxWithErrno function.
+func (dut *DUT) SetSockOptWithErrno(ctx context.Context, sockfd, level, optname int32, optval []byte) (int32, error) {
dut.t.Helper()
req := pb.SetSockOptRequest{
Sockfd: sockfd,
@@ -253,8 +346,6 @@ func (dut *DUT) SetSockOptWithErrno(sockfd, level, optname int32, optval []byte)
Optname: optname,
Optval: optval,
}
- ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout)
- defer cancel()
resp, err := dut.posixServer.SetSockOpt(ctx, &req)
if err != nil {
dut.t.Fatalf("failed to call SetSockOpt: %s", err)
@@ -262,19 +353,51 @@ func (dut *DUT) SetSockOptWithErrno(sockfd, level, optname int32, optval []byte)
return resp.GetRet(), syscall.Errno(resp.GetErrno_())
}
-// SetSockOpt calls setsockopt on the DUT and causes a fatal test failure if it
-// doesn't succeed.
-func (dut *DUT) SetSockOpt(sockfd, level, optname int32, optval []byte) {
+// SetSockOptInt calls setsockopt on the DUT and causes a fatal test failure
+// if it doesn't succeed. If more control over the int optval or error handling
+// is needed, use SetSockOptIntWithErrno.
+func (dut *DUT) SetSockOptInt(sockfd, level, optname, optval int32) {
dut.t.Helper()
- ret, err := dut.SetSockOptWithErrno(sockfd, level, optname, optval)
+ ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout)
+ defer cancel()
+ ret, err := dut.SetSockOptIntWithErrno(ctx, sockfd, level, optname, optval)
if ret != 0 {
- dut.t.Fatalf("failed to SetSockOpt: %s", err)
+ dut.t.Fatalf("failed to SetSockOptInt: %s", err)
+ }
+}
+
+// SetSockOptIntWithErrno calls setsockopt with an integer optval.
+func (dut *DUT) SetSockOptIntWithErrno(ctx context.Context, sockfd, level, optname, optval int32) (int32, error) {
+ dut.t.Helper()
+ req := pb.SetSockOptIntRequest{
+ Sockfd: sockfd,
+ Level: level,
+ Optname: optname,
+ Intval: optval,
+ }
+ resp, err := dut.posixServer.SetSockOptInt(ctx, &req)
+ if err != nil {
+ dut.t.Fatalf("failed to call SetSockOptInt: %s", err)
+ }
+ return resp.GetRet(), syscall.Errno(resp.GetErrno_())
+}
+
+// SetSockOptTimeval calls setsockopt on the DUT and causes a fatal test failure
+// if it doesn't succeed. If more control over the timeout or error handling is
+// needed, use SetSockOptTimevalWithErrno.
+func (dut *DUT) SetSockOptTimeval(sockfd, level, optname int32, tv *unix.Timeval) {
+ dut.t.Helper()
+ ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout)
+ defer cancel()
+ ret, err := dut.SetSockOptTimevalWithErrno(ctx, sockfd, level, optname, tv)
+ if ret != 0 {
+ dut.t.Fatalf("failed to SetSockOptTimeval: %s", err)
}
}
// SetSockOptTimevalWithErrno calls setsockopt with the timeval converted to
// bytes.
-func (dut *DUT) SetSockOptTimevalWithErrno(sockfd, level, optname int32, tv *unix.Timeval) (int32, error) {
+func (dut *DUT) SetSockOptTimevalWithErrno(ctx context.Context, sockfd, level, optname int32, tv *unix.Timeval) (int32, error) {
dut.t.Helper()
timeval := pb.Timeval{
Seconds: int64(tv.Sec),
@@ -286,8 +409,6 @@ func (dut *DUT) SetSockOptTimevalWithErrno(sockfd, level, optname int32, tv *uni
Optname: optname,
Timeval: &timeval,
}
- ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout)
- defer cancel()
resp, err := dut.posixServer.SetSockOptTimeval(ctx, &req)
if err != nil {
dut.t.Fatalf("failed to call SetSockOptTimeval: %s", err)
@@ -295,69 +416,58 @@ func (dut *DUT) SetSockOptTimevalWithErrno(sockfd, level, optname int32, tv *uni
return resp.GetRet(), syscall.Errno(resp.GetErrno_())
}
-// SetSockOptTimeval calls setsockopt on the DUT and causes a fatal test failure
-// if it doesn't succeed.
-func (dut *DUT) SetSockOptTimeval(sockfd, level, optname int32, tv *unix.Timeval) {
+// Socket calls socket on the DUT and returns the file descriptor. If socket
+// fails on the DUT, the test ends.
+func (dut *DUT) Socket(domain, typ, proto int32) int32 {
dut.t.Helper()
- ret, err := dut.SetSockOptTimevalWithErrno(sockfd, level, optname, tv)
- if ret != 0 {
- dut.t.Fatalf("failed to SetSockOptTimeval: %s", err)
+ fd, err := dut.SocketWithErrno(domain, typ, proto)
+ if fd < 0 {
+ dut.t.Fatalf("failed to create socket: %s", err)
}
+ return fd
}
-// CloseWithErrno calls close on the DUT.
-func (dut *DUT) CloseWithErrno(fd int32) (int32, error) {
+// SocketWithErrno calls socket on the DUT and returns the fd and errno.
+func (dut *DUT) SocketWithErrno(domain, typ, proto int32) (int32, error) {
dut.t.Helper()
- req := pb.CloseRequest{
- Fd: fd,
+ req := pb.SocketRequest{
+ Domain: domain,
+ Type: typ,
+ Protocol: proto,
}
- ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout)
- defer cancel()
- resp, err := dut.posixServer.Close(ctx, &req)
+ ctx := context.Background()
+ resp, err := dut.posixServer.Socket(ctx, &req)
if err != nil {
- dut.t.Fatalf("failed to call Close: %s", err)
+ dut.t.Fatalf("failed to call Socket: %s", err)
}
- return resp.GetRet(), syscall.Errno(resp.GetErrno_())
+ return resp.GetFd(), syscall.Errno(resp.GetErrno_())
}
-// Close calls close on the DUT and causes a fatal test failure if it doesn't
-// succeed.
-func (dut *DUT) Close(fd int32) {
+// Recv calls recv on the DUT and causes a fatal test failure if it doesn't
+// succeed. If more control over the timeout or error handling is needed, use
+// RecvWithErrno.
+func (dut *DUT) Recv(sockfd, len, flags int32) []byte {
dut.t.Helper()
- ret, err := dut.CloseWithErrno(fd)
- if ret != 0 {
- dut.t.Fatalf("failed to close: %s", err)
+ ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout)
+ defer cancel()
+ ret, buf, err := dut.RecvWithErrno(ctx, sockfd, len, flags)
+ if ret == -1 {
+ dut.t.Fatalf("failed to recv: %s", err)
}
+ return buf
}
-// CreateListener makes a new TCP connection. If it fails, the test ends.
-func (dut *DUT) CreateListener(typ, proto, backlog int32) (int32, uint16) {
+// RecvWithErrno calls recv on the DUT.
+func (dut *DUT) RecvWithErrno(ctx context.Context, sockfd, len, flags int32) (int32, []byte, error) {
dut.t.Helper()
- addr := net.ParseIP(*remoteIPv4)
- var fd int32
- if addr.To4() != nil {
- fd = dut.Socket(unix.AF_INET, typ, proto)
- sa := unix.SockaddrInet4{}
- copy(sa.Addr[:], addr.To4())
- dut.Bind(fd, &sa)
- } else if addr.To16() != nil {
- fd = dut.Socket(unix.AF_INET6, typ, proto)
- sa := unix.SockaddrInet6{}
- copy(sa.Addr[:], addr.To16())
- dut.Bind(fd, &sa)
- } else {
- dut.t.Fatal("unknown ip addr type for remoteIP")
+ req := pb.RecvRequest{
+ Sockfd: sockfd,
+ Len: len,
+ Flags: flags,
}
- sa := dut.GetSockName(fd)
- var port int
- switch s := sa.(type) {
- case *unix.SockaddrInet4:
- port = s.Port
- case *unix.SockaddrInet6:
- port = s.Port
- default:
- dut.t.Fatalf("unknown sockaddr type from getsockname: %t", sa)
+ resp, err := dut.posixServer.Recv(ctx, &req)
+ if err != nil {
+ dut.t.Fatalf("failed to call Recv: %s", err)
}
- dut.Listen(fd, backlog)
- return fd, uint16(port)
+ return resp.GetRet(), resp.GetBuf(), syscall.Errno(resp.GetErrno_())
}
diff --git a/test/packetimpact/testbench/layers.go b/test/packetimpact/testbench/layers.go
index 35fa4dcb6..b467c15cc 100644
--- a/test/packetimpact/testbench/layers.go
+++ b/test/packetimpact/testbench/layers.go
@@ -17,11 +17,13 @@ package testbench
import (
"fmt"
"reflect"
+ "strings"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/imdario/mergo"
"gvisor.dev/gvisor/pkg/tcpip"
+ "gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header"
)
@@ -31,6 +33,8 @@ import (
// Layer contains all the fields of the encapsulation. Each field is a pointer
// and may be nil.
type Layer interface {
+ fmt.Stringer
+
// toBytes converts the Layer into bytes. In places where the Layer's field
// isn't nil, the value that is pointed to is used. When the field is nil, a
// reasonable default for the Layer is used. For example, "64" for IPv4 TTL
@@ -42,7 +46,8 @@ type Layer interface {
// match checks if the current Layer matches the provided Layer. If either
// Layer has a nil in a given field, that field is considered matching.
- // Otherwise, the values pointed to by the fields must match.
+ // Otherwise, the values pointed to by the fields must match. The LayerBase is
+ // ignored.
match(Layer) bool
// length in bytes of the current encapsulation
@@ -83,21 +88,42 @@ func (lb *LayerBase) setPrev(l Layer) {
lb.prevLayer = l
}
+// equalLayer compares that two Layer structs match while ignoring field in
+// which either input has a nil and also ignoring the LayerBase of the inputs.
func equalLayer(x, y Layer) bool {
+ // opt ignores comparison pairs where either of the inputs is a nil.
opt := cmp.FilterValues(func(x, y interface{}) bool {
- if reflect.ValueOf(x).Kind() == reflect.Ptr && reflect.ValueOf(x).IsNil() {
- return true
- }
- if reflect.ValueOf(y).Kind() == reflect.Ptr && reflect.ValueOf(y).IsNil() {
- return true
+ for _, l := range []interface{}{x, y} {
+ v := reflect.ValueOf(l)
+ if (v.Kind() == reflect.Ptr || v.Kind() == reflect.Slice) && v.IsNil() {
+ return true
+ }
}
return false
-
}, cmp.Ignore())
- return cmp.Equal(x, y, opt, cmpopts.IgnoreUnexported(LayerBase{}))
+ return cmp.Equal(x, y, opt, cmpopts.IgnoreTypes(LayerBase{}))
+}
+
+func stringLayer(l Layer) string {
+ v := reflect.ValueOf(l).Elem()
+ t := v.Type()
+ var ret []string
+ for i := 0; i < v.NumField(); i++ {
+ t := t.Field(i)
+ if t.Anonymous {
+ // Ignore the LayerBase in the Layer struct.
+ continue
+ }
+ v := v.Field(i)
+ if v.IsNil() {
+ continue
+ }
+ ret = append(ret, fmt.Sprintf("%s:%v", t.Name, reflect.Indirect(v)))
+ }
+ return fmt.Sprintf("&%s{%s}", t, strings.Join(ret, " "))
}
-// Ether can construct and match the ethernet encapsulation.
+// Ether can construct and match an ethernet encapsulation.
type Ether struct {
LayerBase
SrcAddr *tcpip.LinkAddress
@@ -105,6 +131,10 @@ type Ether struct {
Type *tcpip.NetworkProtocolNumber
}
+func (l *Ether) String() string {
+ return stringLayer(l)
+}
+
func (l *Ether) toBytes() ([]byte, error) {
b := make([]byte, header.EthernetMinimumSize)
h := header.Ethernet(b)
@@ -123,7 +153,7 @@ func (l *Ether) toBytes() ([]byte, error) {
fields.Type = header.IPv4ProtocolNumber
default:
// TODO(b/150301488): Support more protocols, like IPv6.
- return nil, fmt.Errorf("can't deduce the ethernet header's next protocol: %d", n)
+ return nil, fmt.Errorf("ethernet header's next layer is unrecognized: %#v", n)
}
}
h.Encode(fields)
@@ -142,27 +172,46 @@ func NetworkProtocolNumber(v tcpip.NetworkProtocolNumber) *tcpip.NetworkProtocol
return &v
}
+// LayerParser parses the input bytes and returns a Layer along with the next
+// LayerParser to run. If there is no more parsing to do, the returned
+// LayerParser is nil.
+type LayerParser func([]byte) (Layer, LayerParser)
+
+// Parse parses bytes starting with the first LayerParser and using successive
+// LayerParsers until all the bytes are parsed.
+func Parse(parser LayerParser, b []byte) Layers {
+ var layers Layers
+ for {
+ var layer Layer
+ layer, parser = parser(b)
+ layers = append(layers, layer)
+ if parser == nil {
+ break
+ }
+ b = b[layer.length():]
+ }
+ layers.linkLayers()
+ return layers
+}
+
// ParseEther parses the bytes assuming that they start with an ethernet header
// and continues parsing further encapsulations.
-func ParseEther(b []byte) (Layers, error) {
+func ParseEther(b []byte) (Layer, LayerParser) {
h := header.Ethernet(b)
ether := Ether{
SrcAddr: LinkAddress(h.SourceAddress()),
DstAddr: LinkAddress(h.DestinationAddress()),
Type: NetworkProtocolNumber(h.Type()),
}
- layers := Layers{&ether}
+ var nextParser LayerParser
switch h.Type() {
case header.IPv4ProtocolNumber:
- moreLayers, err := ParseIPv4(b[ether.length():])
- if err != nil {
- return nil, err
- }
- return append(layers, moreLayers...), nil
+ nextParser = ParseIPv4
default:
- // TODO(b/150301488): Support more protocols, like IPv6.
- return nil, fmt.Errorf("can't deduce the ethernet header's next protocol: %v", b)
+ // Assume that the rest is a payload.
+ nextParser = ParsePayload
}
+ return &ether, nextParser
}
func (l *Ether) match(other Layer) bool {
@@ -173,7 +222,7 @@ func (l *Ether) length() int {
return header.EthernetMinimumSize
}
-// IPv4 can construct and match the ethernet excapulation.
+// IPv4 can construct and match an IPv4 encapsulation.
type IPv4 struct {
LayerBase
IHL *uint8
@@ -189,6 +238,10 @@ type IPv4 struct {
DstAddr *tcpip.Address
}
+func (l *IPv4) String() string {
+ return stringLayer(l)
+}
+
func (l *IPv4) toBytes() ([]byte, error) {
b := make([]byte, header.IPv4MinimumSize)
h := header.IPv4(b)
@@ -236,9 +289,11 @@ func (l *IPv4) toBytes() ([]byte, error) {
switch n := l.next().(type) {
case *TCP:
fields.Protocol = uint8(header.TCPProtocolNumber)
+ case *UDP:
+ fields.Protocol = uint8(header.UDPProtocolNumber)
default:
- // TODO(b/150301488): Support more protocols, like UDP.
- return nil, fmt.Errorf("can't deduce the ip header's next protocol: %+v", n)
+ // TODO(b/150301488): Support more protocols as needed.
+ return nil, fmt.Errorf("ipv4 header's next layer is unrecognized: %#v", n)
}
}
if l.SrcAddr != nil {
@@ -277,7 +332,7 @@ func Address(v tcpip.Address) *tcpip.Address {
// ParseIPv4 parses the bytes assuming that they start with an ipv4 header and
// continues parsing further encapsulations.
-func ParseIPv4(b []byte) (Layers, error) {
+func ParseIPv4(b []byte) (Layer, LayerParser) {
h := header.IPv4(b)
tos, _ := h.TOS()
ipv4 := IPv4{
@@ -293,16 +348,17 @@ func ParseIPv4(b []byte) (Layers, error) {
SrcAddr: Address(h.SourceAddress()),
DstAddr: Address(h.DestinationAddress()),
}
- layers := Layers{&ipv4}
- switch h.Protocol() {
- case uint8(header.TCPProtocolNumber):
- moreLayers, err := ParseTCP(b[ipv4.length():])
- if err != nil {
- return nil, err
- }
- return append(layers, moreLayers...), nil
+ var nextParser LayerParser
+ switch h.TransportProtocol() {
+ case header.TCPProtocolNumber:
+ nextParser = ParseTCP
+ case header.UDPProtocolNumber:
+ nextParser = ParseUDP
+ default:
+ // Assume that the rest is a payload.
+ nextParser = ParsePayload
}
- return nil, fmt.Errorf("can't deduce the ethernet header's next protocol: %d", h.Protocol())
+ return &ipv4, nextParser
}
func (l *IPv4) match(other Layer) bool {
@@ -316,7 +372,7 @@ func (l *IPv4) length() int {
return int(*l.IHL)
}
-// TCP can construct and match the TCP excapulation.
+// TCP can construct and match a TCP encapsulation.
type TCP struct {
LayerBase
SrcPort *uint16
@@ -330,6 +386,10 @@ type TCP struct {
UrgentPointer *uint16
}
+func (l *TCP) String() string {
+ return stringLayer(l)
+}
+
func (l *TCP) toBytes() ([]byte, error) {
b := make([]byte, header.TCPMinimumSize)
h := header.TCP(b)
@@ -347,12 +407,16 @@ func (l *TCP) toBytes() ([]byte, error) {
}
if l.DataOffset != nil {
h.SetDataOffset(*l.DataOffset)
+ } else {
+ h.SetDataOffset(uint8(l.length()))
}
if l.Flags != nil {
h.SetFlags(*l.Flags)
}
if l.WindowSize != nil {
h.SetWindowSize(*l.WindowSize)
+ } else {
+ h.SetWindowSize(32768)
}
if l.UrgentPointer != nil {
h.SetUrgentPoiner(*l.UrgentPointer)
@@ -361,38 +425,52 @@ func (l *TCP) toBytes() ([]byte, error) {
h.SetChecksum(*l.Checksum)
return h, nil
}
- if err := setChecksum(&h, l); err != nil {
+ if err := setTCPChecksum(&h, l); err != nil {
return nil, err
}
return h, nil
}
-// setChecksum calculates the checksum of the TCP header and sets it in h.
-func setChecksum(h *header.TCP, tcp *TCP) error {
- h.SetChecksum(0)
- tcpLength := uint16(tcp.length())
- current := tcp.next()
- for current != nil {
- tcpLength += uint16(current.length())
- current = current.next()
+// totalLength returns the length of the provided layer and all following
+// layers.
+func totalLength(l Layer) int {
+ var totalLength int
+ for ; l != nil; l = l.next() {
+ totalLength += l.length()
}
+ return totalLength
+}
+// layerChecksum calculates the checksum of the Layer header, including the
+// peusdeochecksum of the layer before it and all the bytes after it..
+func layerChecksum(l Layer, protoNumber tcpip.TransportProtocolNumber) (uint16, error) {
+ totalLength := uint16(totalLength(l))
var xsum uint16
- switch s := tcp.prev().(type) {
+ switch s := l.prev().(type) {
case *IPv4:
- xsum = header.PseudoHeaderChecksum(header.TCPProtocolNumber, *s.SrcAddr, *s.DstAddr, tcpLength)
+ xsum = header.PseudoHeaderChecksum(protoNumber, *s.SrcAddr, *s.DstAddr, totalLength)
default:
// TODO(b/150301488): Support more protocols, like IPv6.
- return fmt.Errorf("can't get src and dst addr from previous layer")
+ return 0, fmt.Errorf("can't get src and dst addr from previous layer: %#v", s)
}
- current = tcp.next()
- for current != nil {
+ var payloadBytes buffer.VectorisedView
+ for current := l.next(); current != nil; current = current.next() {
payload, err := current.toBytes()
if err != nil {
- return fmt.Errorf("can't get bytes for next header: %s", payload)
+ return 0, fmt.Errorf("can't get bytes for next header: %s", payload)
}
- xsum = header.Checksum(payload, xsum)
- current = current.next()
+ payloadBytes.AppendView(payload)
+ }
+ xsum = header.ChecksumVV(payloadBytes, xsum)
+ return xsum, nil
+}
+
+// setTCPChecksum calculates the checksum of the TCP header and sets it in h.
+func setTCPChecksum(h *header.TCP, tcp *TCP) error {
+ h.SetChecksum(0)
+ xsum, err := layerChecksum(tcp, header.TCPProtocolNumber)
+ if err != nil {
+ return err
}
h.SetChecksum(^h.CalculateChecksum(xsum))
return nil
@@ -406,7 +484,7 @@ func Uint32(v uint32) *uint32 {
// ParseTCP parses the bytes assuming that they start with a tcp header and
// continues parsing further encapsulations.
-func ParseTCP(b []byte) (Layers, error) {
+func ParseTCP(b []byte) (Layer, LayerParser) {
h := header.TCP(b)
tcp := TCP{
SrcPort: Uint16(h.SourcePort()),
@@ -419,12 +497,7 @@ func ParseTCP(b []byte) (Layers, error) {
Checksum: Uint16(h.Checksum()),
UrgentPointer: Uint16(h.UrgentPointer()),
}
- layers := Layers{&tcp}
- moreLayers, err := ParsePayload(b[tcp.length():])
- if err != nil {
- return nil, err
- }
- return append(layers, moreLayers...), nil
+ return &tcp, ParsePayload
}
func (l *TCP) match(other Layer) bool {
@@ -444,19 +517,101 @@ func (l *TCP) merge(other TCP) error {
return mergo.Merge(l, other, mergo.WithOverride)
}
+// UDP can construct and match a UDP encapsulation.
+type UDP struct {
+ LayerBase
+ SrcPort *uint16
+ DstPort *uint16
+ Length *uint16
+ Checksum *uint16
+}
+
+func (l *UDP) String() string {
+ return stringLayer(l)
+}
+
+func (l *UDP) toBytes() ([]byte, error) {
+ b := make([]byte, header.UDPMinimumSize)
+ h := header.UDP(b)
+ if l.SrcPort != nil {
+ h.SetSourcePort(*l.SrcPort)
+ }
+ if l.DstPort != nil {
+ h.SetDestinationPort(*l.DstPort)
+ }
+ if l.Length != nil {
+ h.SetLength(*l.Length)
+ } else {
+ h.SetLength(uint16(totalLength(l)))
+ }
+ if l.Checksum != nil {
+ h.SetChecksum(*l.Checksum)
+ return h, nil
+ }
+ if err := setUDPChecksum(&h, l); err != nil {
+ return nil, err
+ }
+ return h, nil
+}
+
+// setUDPChecksum calculates the checksum of the UDP header and sets it in h.
+func setUDPChecksum(h *header.UDP, udp *UDP) error {
+ h.SetChecksum(0)
+ xsum, err := layerChecksum(udp, header.UDPProtocolNumber)
+ if err != nil {
+ return err
+ }
+ h.SetChecksum(^h.CalculateChecksum(xsum))
+ return nil
+}
+
+// ParseUDP parses the bytes assuming that they start with a udp header and
+// returns the parsed layer and the next parser to use.
+func ParseUDP(b []byte) (Layer, LayerParser) {
+ h := header.UDP(b)
+ udp := UDP{
+ SrcPort: Uint16(h.SourcePort()),
+ DstPort: Uint16(h.DestinationPort()),
+ Length: Uint16(h.Length()),
+ Checksum: Uint16(h.Checksum()),
+ }
+ return &udp, ParsePayload
+}
+
+func (l *UDP) match(other Layer) bool {
+ return equalLayer(l, other)
+}
+
+func (l *UDP) length() int {
+ if l.Length == nil {
+ return header.UDPMinimumSize
+ }
+ return int(*l.Length)
+}
+
+// merge overrides the values in l with the values from other but only in fields
+// where the value is not nil.
+func (l *UDP) merge(other UDP) error {
+ return mergo.Merge(l, other, mergo.WithOverride)
+}
+
// Payload has bytes beyond OSI layer 4.
type Payload struct {
LayerBase
Bytes []byte
}
+func (l *Payload) String() string {
+ return stringLayer(l)
+}
+
// ParsePayload parses the bytes assuming that they start with a payload and
// continue to the end. There can be no further encapsulations.
-func ParsePayload(b []byte) (Layers, error) {
+func ParsePayload(b []byte) (Layer, LayerParser) {
payload := Payload{
Bytes: b,
}
- return Layers{&payload}, nil
+ return &payload, nil
}
func (l *Payload) toBytes() ([]byte, error) {
@@ -474,15 +629,24 @@ func (l *Payload) length() int {
// Layers is an array of Layer and supports similar functions to Layer.
type Layers []Layer
-func (ls *Layers) toBytes() ([]byte, error) {
+// linkLayers sets the linked-list ponters in ls.
+func (ls *Layers) linkLayers() {
for i, l := range *ls {
if i > 0 {
l.setPrev((*ls)[i-1])
+ } else {
+ l.setPrev(nil)
}
if i+1 < len(*ls) {
l.setNext((*ls)[i+1])
+ } else {
+ l.setNext(nil)
}
}
+}
+
+func (ls *Layers) toBytes() ([]byte, error) {
+ ls.linkLayers()
outBytes := []byte{}
for _, l := range *ls {
layerBytes, err := l.toBytes()
diff --git a/test/packetimpact/testbench/layers_test.go b/test/packetimpact/testbench/layers_test.go
new file mode 100644
index 000000000..8ffc26bf9
--- /dev/null
+++ b/test/packetimpact/testbench/layers_test.go
@@ -0,0 +1,144 @@
+// Copyright 2020 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package testbench
+
+import "testing"
+
+import "gvisor.dev/gvisor/pkg/tcpip"
+
+func TestLayerMatch(t *testing.T) {
+ var nilPayload *Payload
+ noPayload := &Payload{}
+ emptyPayload := &Payload{Bytes: []byte{}}
+ fullPayload := &Payload{Bytes: []byte{1, 2, 3}}
+ emptyTCP := &TCP{SrcPort: Uint16(1234), LayerBase: LayerBase{nextLayer: emptyPayload}}
+ fullTCP := &TCP{SrcPort: Uint16(1234), LayerBase: LayerBase{nextLayer: fullPayload}}
+ for _, tt := range []struct {
+ a, b Layer
+ want bool
+ }{
+ {nilPayload, nilPayload, true},
+ {nilPayload, noPayload, true},
+ {nilPayload, emptyPayload, true},
+ {nilPayload, fullPayload, true},
+ {noPayload, noPayload, true},
+ {noPayload, emptyPayload, true},
+ {noPayload, fullPayload, true},
+ {emptyPayload, emptyPayload, true},
+ {emptyPayload, fullPayload, false},
+ {fullPayload, fullPayload, true},
+ {emptyTCP, fullTCP, true},
+ } {
+ if got := tt.a.match(tt.b); got != tt.want {
+ t.Errorf("%s.match(%s) = %t, want %t", tt.a, tt.b, got, tt.want)
+ }
+ if got := tt.b.match(tt.a); got != tt.want {
+ t.Errorf("%s.match(%s) = %t, want %t", tt.b, tt.a, got, tt.want)
+ }
+ }
+}
+
+func TestLayerStringFormat(t *testing.T) {
+ for _, tt := range []struct {
+ name string
+ l Layer
+ want string
+ }{
+ {
+ name: "TCP",
+ l: &TCP{
+ SrcPort: Uint16(34785),
+ DstPort: Uint16(47767),
+ SeqNum: Uint32(3452155723),
+ AckNum: Uint32(2596996163),
+ DataOffset: Uint8(5),
+ Flags: Uint8(20),
+ WindowSize: Uint16(64240),
+ Checksum: Uint16(0x2e2b),
+ },
+ want: "&testbench.TCP{" +
+ "SrcPort:34785 " +
+ "DstPort:47767 " +
+ "SeqNum:3452155723 " +
+ "AckNum:2596996163 " +
+ "DataOffset:5 " +
+ "Flags:20 " +
+ "WindowSize:64240 " +
+ "Checksum:11819" +
+ "}",
+ },
+ {
+ name: "UDP",
+ l: &UDP{
+ SrcPort: Uint16(34785),
+ DstPort: Uint16(47767),
+ Length: Uint16(12),
+ },
+ want: "&testbench.UDP{" +
+ "SrcPort:34785 " +
+ "DstPort:47767 " +
+ "Length:12" +
+ "}",
+ },
+ {
+ name: "IPv4",
+ l: &IPv4{
+ IHL: Uint8(5),
+ TOS: Uint8(0),
+ TotalLength: Uint16(44),
+ ID: Uint16(0),
+ Flags: Uint8(2),
+ FragmentOffset: Uint16(0),
+ TTL: Uint8(64),
+ Protocol: Uint8(6),
+ Checksum: Uint16(0x2e2b),
+ SrcAddr: Address(tcpip.Address([]byte{197, 34, 63, 10})),
+ DstAddr: Address(tcpip.Address([]byte{197, 34, 63, 20})),
+ },
+ want: "&testbench.IPv4{" +
+ "IHL:5 " +
+ "TOS:0 " +
+ "TotalLength:44 " +
+ "ID:0 " +
+ "Flags:2 " +
+ "FragmentOffset:0 " +
+ "TTL:64 " +
+ "Protocol:6 " +
+ "Checksum:11819 " +
+ "SrcAddr:197.34.63.10 " +
+ "DstAddr:197.34.63.20" +
+ "}",
+ },
+ {
+ name: "Ether",
+ l: &Ether{
+ SrcAddr: LinkAddress(tcpip.LinkAddress([]byte{0x02, 0x42, 0xc5, 0x22, 0x3f, 0x0a})),
+ DstAddr: LinkAddress(tcpip.LinkAddress([]byte{0x02, 0x42, 0xc5, 0x22, 0x3f, 0x14})),
+ Type: NetworkProtocolNumber(4),
+ },
+ want: "&testbench.Ether{" +
+ "SrcAddr:02:42:c5:22:3f:0a " +
+ "DstAddr:02:42:c5:22:3f:14 " +
+ "Type:4" +
+ "}",
+ },
+ } {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := tt.l.String(); got != tt.want {
+ t.Errorf("%s.String() = %s, want: %s", tt.name, got, tt.want)
+ }
+ })
+ }
+}
diff --git a/test/packetimpact/testbench/rawsockets.go b/test/packetimpact/testbench/rawsockets.go
index 0c7d0f979..0074484f7 100644
--- a/test/packetimpact/testbench/rawsockets.go
+++ b/test/packetimpact/testbench/rawsockets.go
@@ -47,6 +47,12 @@ func NewSniffer(t *testing.T) (Sniffer, error) {
if err != nil {
return Sniffer{}, err
}
+ if err := unix.SetsockoptInt(snifferFd, unix.SOL_SOCKET, unix.SO_RCVBUFFORCE, 1); err != nil {
+ t.Fatalf("can't set sockopt SO_RCVBUFFORCE to 1: %s", err)
+ }
+ if err := unix.SetsockoptInt(snifferFd, unix.SOL_SOCKET, unix.SO_RCVBUF, 1e7); err != nil {
+ t.Fatalf("can't setsockopt SO_RCVBUF to 10M: %s", err)
+ }
return Sniffer{
t: t,
fd: snifferFd,