summaryrefslogtreecommitdiffhomepage
path: root/test/packetimpact
diff options
context:
space:
mode:
Diffstat (limited to 'test/packetimpact')
-rw-r--r--test/packetimpact/README.md681
-rw-r--r--test/packetimpact/dut/BUILD18
-rw-r--r--test/packetimpact/dut/posix_server.cc260
-rw-r--r--test/packetimpact/proto/BUILD12
-rw-r--r--test/packetimpact/proto/posix_server.proto193
-rw-r--r--test/packetimpact/testbench/BUILD43
-rw-r--r--test/packetimpact/testbench/connections.go824
-rw-r--r--test/packetimpact/testbench/dut.go473
-rw-r--r--test/packetimpact/testbench/dut_client.go28
-rw-r--r--test/packetimpact/testbench/layers.go1157
-rw-r--r--test/packetimpact/testbench/layers_test.go395
-rw-r--r--test/packetimpact/testbench/rawsockets.go183
-rw-r--r--test/packetimpact/tests/BUILD115
-rw-r--r--test/packetimpact/tests/defs.bzl137
-rw-r--r--test/packetimpact/tests/fin_wait2_timeout_test.go70
-rw-r--r--test/packetimpact/tests/icmpv6_param_problem_test.go73
-rw-r--r--test/packetimpact/tests/tcp_close_wait_ack_test.go102
-rw-r--r--test/packetimpact/tests/tcp_noaccept_close_rst_test.go37
-rw-r--r--test/packetimpact/tests/tcp_outside_the_window_test.go88
-rw-r--r--test/packetimpact/tests/tcp_should_piggyback_test.go59
-rw-r--r--test/packetimpact/tests/tcp_user_timeout_test.go100
-rw-r--r--test/packetimpact/tests/tcp_window_shrink_test.go68
-rwxr-xr-xtest/packetimpact/tests/test_runner.sh325
-rw-r--r--test/packetimpact/tests/udp_recv_multicast_test.go37
24 files changed, 0 insertions, 5478 deletions
diff --git a/test/packetimpact/README.md b/test/packetimpact/README.md
deleted file mode 100644
index a82ad996a..000000000
--- a/test/packetimpact/README.md
+++ /dev/null
@@ -1,681 +0,0 @@
-# Packetimpact
-
-## What is packetimpact?
-
-Packetimpact is a tool for platform-independent network testing. It is heavily
-inspired by [packetdrill](https://github.com/google/packetdrill). It creates two
-docker containers connected by a network. One is for the test bench, which
-operates the test. The other is for the device-under-test (DUT), which is the
-software being tested. The test bench communicates over the network with the DUT
-to check correctness of the network.
-
-### Goals
-
-Packetimpact aims to provide:
-
-* A **multi-platform** solution that can test both Linux and gVisor.
-* **Conciseness** on par with packetdrill scripts.
-* **Control-flow** like for loops, conditionals, and variables.
-* **Flexibilty** to specify every byte in a packet or use multiple sockets.
-
-## When to use packetimpact?
-
-There are a few ways to write networking tests for gVisor currently:
-
-* [Go unit tests](https://github.com/google/gvisor/tree/master/pkg/tcpip)
-* [syscall tests](https://github.com/google/gvisor/tree/master/test/syscalls/linux)
-* [packetdrill tests](https://github.com/google/gvisor/tree/master/test/packetdrill)
-* packetimpact tests
-
-The right choice depends on the needs of the test.
-
-Feature | Go unit test | syscall test | packetdrill | packetimpact
--------------- | ------------ | ------------ | ----------- | ------------
-Multi-platform | no | **YES** | **YES** | **YES**
-Concise | no | somewhat | somewhat | **VERY**
-Control-flow | **YES** | **YES** | no | **YES**
-Flexible | **VERY** | no | somewhat | **VERY**
-
-### Go unit tests
-
-If the test depends on the internals of gVisor and doesn't need to run on Linux
-or other platforms for comparison purposes, a Go unit test can be appropriate.
-They can observe internals of gVisor networking. The downside is that they are
-**not concise** and **not multi-platform**. If you require insight on gVisor
-internals, this is the right choice.
-
-### Syscall tests
-
-Syscall tests are **multi-platform** but cannot examine the internals of gVisor
-networking. They are **concise**. They can use **control-flow** structures like
-conditionals, for loops, and variables. However, they are limited to only what
-the POSIX interface provides so they are **not flexible**. For example, you
-would have difficulty writing a syscall test that intentionally sends a bad IP
-checksum. Or if you did write that test with raw sockets, it would be very
-**verbose** to write a test that intentionally send wrong checksums, wrong
-protocols, wrong sequence numbers, etc.
-
-### Packetdrill tests
-
-Packetdrill tests are **multi-platform** and can run against both Linux and
-gVisor. They are **concise** and use a special packetdrill scripting language.
-They are **more flexible** than a syscall test in that they can send packets
-that a syscall test would have difficulty sending, like a packet with a
-calcuated ACK number. But they are also somewhat limimted in flexibiilty in that
-they can't do tests with multiple sockets. They have **no control-flow** ability
-like variables or conditionals. For example, it isn't possible to send a packet
-that depends on the window size of a previous packet because the packetdrill
-language can't express that. Nor could you branch based on whether or not the
-other side supports window scaling, for example.
-
-### Packetimpact tests
-
-Packetimpact tests are similar to Packetdrill tests except that they are written
-in Go instead of the packetdrill scripting language. That gives them all the
-**control-flow** abilities of Go (loops, functions, variables, etc). They are
-**multi-platform** in the same way as packetdrill tests but even more
-**flexible** because Go is more expressive than the scripting language of
-packetdrill. However, Go is **not as concise** as the packetdrill language. Many
-design decisions below are made to mitigate that.
-
-## How it works
-
-```
- Testbench Device-Under-Test (DUT)
- +-------------------+ +------------------------+
- | | TEST NET | |
- | rawsockets.go <-->| <===========> | <---+ |
- | ^ | | | |
- | | | | | |
- | v | | | |
- | unittest | | | |
- | ^ | | | |
- | | | | | |
- | v | | v |
- | dut.go <========gRPC========> posix server |
- | | CONTROL NET | |
- +-------------------+ +------------------------+
-```
-
-Two docker containers are created by a "runner" script, one for the testbench
-and the other for the device under test (DUT). The script connects the two
-containers with a control network and test network. It also does some other
-tasks like waiting until the DUT is ready before starting the test and disabling
-Linux networking that would interfere with the test bench.
-
-### DUT
-
-The DUT container runs a program called the "posix_server". The posix_server is
-written in c++ for maximum portability. It is compiled on the host. The script
-that starts the containers copies it into the DUT's container and runs it. It's
-job is to receive directions from the test bench on what actions to take. For
-this, the posix_server does three steps in a loop:
-
-1. Listen for a request from the test bench.
-2. Execute a command.
-3. Send the response back to the test bench.
-
-The requests and responses are
-[protobufs](https://developers.google.com/protocol-buffers) and the
-communication is done with [gRPC](https://grpc.io/). The commands run are
-[POSIX socket commands](https://en.wikipedia.org/wiki/Berkeley_sockets#Socket_API_functions),
-with the inputs and outputs converted into protobuf requests and responses. All
-communication is on the control network, so that the test network is unaffected
-by extra packets.
-
-For example, this is the request and response pair to call
-[`socket()`](http://man7.org/linux/man-pages/man2/socket.2.html):
-
-```protocol-buffer
-message SocketRequest {
- int32 domain = 1;
- int32 type = 2;
- int32 protocol = 3;
-}
-
-message SocketResponse {
- int32 fd = 1;
- int32 errno_ = 2;
-}
-```
-
-##### Alternatives considered
-
-* We could have use JSON for communication instead. It would have been a
- lighter-touch than protobuf but protobuf handles all the data type and has
- strict typing to prevent a class of errors. The test bench could be written
- in other languages, too.
-* Instead of mimicking the POSIX interfaces, arguments could have had a more
- natural form, like the `bind()` getting a string IP address instead of bytes
- in a `sockaddr_t`. However, conforming to the existing structures keeps more
- of the complexity in Go and keeps the posix_server simpler and thus more
- likely to compile everywhere.
-
-### Test Bench
-
-The test bench does most of the work in a test. It is a Go program that compiles
-on the host and is copied by the script into test bench's container. It is a
-regular [go unit test](https://golang.org/pkg/testing/) that imports the test
-bench framework. The test bench framwork is based on three basic utilities:
-
-* Commanding the DUT to run POSIX commands and return responses.
-* Sending raw packets to the DUT on the test network.
-* Listening for raw packets from the DUT on the test network.
-
-#### DUT commands
-
-To keep the interface to the DUT consistent and easy-to-use, each POSIX command
-supported by the posix_server is wrapped in functions with signatures similar to
-the ones in the [Go unix package](https://godoc.org/golang.org/x/sys/unix). This
-way all the details of endianess and (un)marshalling of go structs such as
-[unix.Timeval](https://godoc.org/golang.org/x/sys/unix#Timeval) is handled in
-one place. This also makes it straight-forward to convert tests that use `unix.`
-or `syscall.` calls to `dut.` calls.
-
-For example, creating a connection to the DUT and commanding it to make a socket
-looks like this:
-
-```go
-dut := testbench.NewDut(t)
-fd, err := dut.SocketWithErrno(unix.AF_INET, unix.SOCK_STREAM, unix.IPPROTO_IP)
-if fd < 0 {
- t.Fatalf(...)
-}
-```
-
-Because the usual case is to fail the test when the DUT fails to create a
-socket, there is a concise version of each of the `...WithErrno` functions that
-does that:
-
-```go
-dut := testbench.NewDut(t)
-fd := dut.Socket(unix.AF_INET, unix.SOCK_STREAM, unix.IPPROTO_IP)
-```
-
-The DUT and other structs in the code store a `*testing.T` so that they can
-provide versions of functions that call `t.Fatalf(...)`. This helps keep tests
-concise.
-
-##### Alternatives considered
-
-* Instead of mimicking the `unix.` go interface, we could have invented a more
- natural one, like using `float64` instead of `Timeval`. However, using the
- same function signatures that `unix.` has makes it easier to convert code to
- `dut.`. Also, using an existing interface ensures that we don't invent an
- interface that isn't extensible. For example, if we invented a function for
- `bind()` that didn't support IPv6 and later we had to add a second `bind6()`
- function.
-
-#### Sending/Receiving Raw Packets
-
-The framework wraps POSIX sockets for sending and receiving raw frames. Both
-send and receive are synchronous commands.
-[SO_RCVTIMEO](http://man7.org/linux/man-pages/man7/socket.7.html) is used to set
-a timeout on the receive commands. For ease of use, these are wrapped in an
-`Injector` and a `Sniffer`. They have functions:
-
-```go
-func (s *Sniffer) Recv(timeout time.Duration) []byte {...}
-func (i *Injector) Send(b []byte) {...}
-```
-
-##### Alternatives considered
-
-* [gopacket](https://github.com/google/gopacket) pcap has raw socket support
- but requires cgo. cgo is not guaranteed to be portable from the host to the
- container and in practice, the container doesn't recognize binaries built on
- the host if they use cgo.
-* Both gVisor and gopacket have the ability to read and write pcap files
- without cgo but that is insufficient here because we can't just replay pcap
- files, we need a more dynamic solution.
-* The sniffer and injector can't share a socket because they need to be bound
- differently.
-* Sniffing could have been done asynchronously with channels, obviating the
- need for `SO_RCVTIMEO`. But that would introduce asynchronous complication.
- `SO_RCVTIMEO` is well supported on the test bench.
-
-#### `Layer` struct
-
-A large part of packetimpact tests is creating packets to send and comparing
-received packets against expectations. To keep tests concise, it is useful to be
-able to specify just the important parts of packets that need to be set. For
-example, sending a packet with default values except for TCP Flags. And for
-packets received, it's useful to be able to compare just the necessary parts of
-received packets and ignore the rest.
-
-To aid in both of those, Go structs with optional fields are created for each
-encapsulation type, such as IPv4, TCP, and Ethernet. This is inspired by
-[scapy](https://scapy.readthedocs.io/en/latest/). For example, here is the
-struct for Ethernet:
-
-```go
-type Ether struct {
- LayerBase
- SrcAddr *tcpip.LinkAddress
- DstAddr *tcpip.LinkAddress
- Type *tcpip.NetworkProtocolNumber
-}
-```
-
-Each struct has the same fields as those in the
-[gVisor headers](https://github.com/google/gvisor/tree/master/pkg/tcpip/header)
-but with a pointer for each field that may be `nil`.
-
-##### Alternatives considered
-
-* Just use []byte like gVisor headers do. The drawback is that it makes the
- tests more verbose.
- * For example, there would be no way to call `Send(myBytes)` concisely and
- indicate if the checksum should be calculated automatically versus
- overridden. The only way would be to add lines to the test to calculate
- it before each Send, which is wordy. Or make multiple versions of Send:
- one that checksums IP, one that doesn't, one that checksums TCP, one
- that does both, etc. That would be many combinations.
- * Filtering inputs would become verbose. Either:
- * large conditionals that need to be repeated many places:
- `h[FlagOffset] == SYN && h[LengthOffset:LengthOffset+2] == ...` or
- * Many functions, one per field, like: `filterByFlag(myBytes, SYN)`,
- `filterByLength(myBytes, 20)`, `filterByNextProto(myBytes, 0x8000)`,
- etc.
- * Using pointers allows us to combine `Layer`s with reflection. So the
- default `Layers` can be overridden by a `Layers` with just the TCP
- conection's src/dst which can be overridden by one with just a test
- specific TCP window size.
- * It's a proven way to separate the details of a packet from the byte
- format as shown by scapy's success.
-* Use packetgo. It's more general than parsing packets with gVisor. However:
- * packetgo doesn't have optional fields so many of the above problems
- still apply.
- * It would be yet another dependency.
- * It's not as well known to engineers that are already writing gVisor
- code.
- * It might be a good candidate for replacing the parsing of packets into
- `Layer`s if all that parsing turns out to be more work than parsing by
- packetgo and converting *that* to `Layer`. packetgo has easier to use
- getters for the layers. This could be done later in a way that doesn't
- break tests.
-
-#### `Layer` methods
-
-The `Layer` structs provide a way to partially specify an encapsulation. They
-also need methods for using those partially specified encapsulation, for example
-to marshal them to bytes or compare them. For those, each encapsulation
-implements the `Layer` interface:
-
-```go
-// Layer is the interface that all encapsulations must implement.
-//
-// A Layer is an encapsulation in a packet, such as TCP, IPv4, IPv6, etc. A
-// Layer contains all the fields of the encapsulation. Each field is a pointer
-// and may be nil.
-type Layer interface {
- // 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
- // and a calculated checksum for TCP or IP. Some layers require information
- // from the previous or next layers in order to compute a default, such as
- // TCP's checksum or Ethernet's type, so each Layer has a doubly-linked list
- // to the layer's neighbors.
- toBytes() ([]byte, error)
-
- // 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.
- match(Layer) bool
-
- // length in bytes of the current encapsulation
- length() int
-
- // next gets a pointer to the encapsulated Layer.
- next() Layer
-
- // prev gets a pointer to the Layer encapsulating this one.
- prev() Layer
-
- // setNext sets the pointer to the encapsulated Layer.
- setNext(Layer)
-
- // setPrev sets the pointer to the Layer encapsulating this one.
- setPrev(Layer)
-}
-```
-
-The `next` and `prev` make up a link listed so that each layer can get at the
-information in the layer around it. This is necessary for some protocols, like
-TCP that needs the layer before and payload after to compute the checksum. Any
-sequence of `Layer` structs is valid so long as the parser and `toBytes`
-functions can map from type to protool number and vice-versa. When the mapping
-fails, an error is emitted explaining what functionality is missing. The
-solution is either to fix the ordering or implement the missing protocol.
-
-For each `Layer` there is also a parsing function. For example, this one is for
-Ethernet:
-
-```
-func ParseEther(b []byte) (Layers, error)
-```
-
-The parsing function converts bytes received on the wire into a `Layer`
-(actually `Layers`, see below) which has no `nil`s in it. By using
-`match(Layer)` to compare against another `Layer` that *does* have `nil`s in it,
-the received bytes can be partially compared. The `nil`s behave as
-"don't-cares".
-
-##### Alternatives considered
-
-* Matching against `[]byte` instead of converting to `Layer` first.
- * The downside is that it precludes the use of a `cmp.Equal` one-liner to
- do comparisons.
- * It creates confusion in the code to deal with both representations at
- different times. For example, is the checksum calculated on `[]byte` or
- `Layer` when sending? What about when checking received packets?
-
-#### `Layers`
-
-```
-type Layers []Layer
-
-func (ls *Layers) match(other Layers) bool {...}
-func (ls *Layers) toBytes() ([]byte, error) {...}
-```
-
-`Layers` is an array of `Layer`. It represents a stack of encapsulations, such
-as `Layers{Ether{},IPv4{},TCP{},Payload{}}`. It also has `toBytes()` and
-`match(Layers)`, like `Layer`. The parse functions above actually return
-`Layers` and not `Layer` because they know about the headers below and
-sequentially call each parser on the remaining, encapsulated bytes.
-
-All this leads to the ability to write concise packet processing. For example:
-
-```go
-etherType := 0x8000
-flags = uint8(header.TCPFlagSyn|header.TCPFlagAck)
-toMatch := Layers{Ether{Type: &etherType}, IPv4{}, TCP{Flags: &flags}}
-for {
- recvBytes := sniffer.Recv(time.Second)
- if recvBytes == nil {
- println("Got no packet for 1 second")
- }
- gotPacket, err := ParseEther(recvBytes)
- if err == nil && toMatch.match(gotPacket) {
- println("Got a TCP/IPv4/Eth packet with SYNACK")
- }
-}
-```
-
-##### Alternatives considered
-
-* Don't use previous and next pointers.
- * Each layer may need to be able to interrogate the layers around it, like
- for computing the next protocol number or total length. So *some*
- mechanism is needed for a `Layer` to see neighboring layers.
- * We could pass the entire array `Layers` to the `toBytes()` function.
- Passing an array to a method that includes in the array the function
- receiver itself seems wrong.
-
-#### `layerState`
-
-`Layers` represents the different headers of a packet but a connection includes
-more state. For example, a TCP connection needs to keep track of the next
-expected sequence number and also the next sequence number to send. This is
-stored in a `layerState` struct. This is the `layerState` for TCP:
-
-```go
-// tcpState maintains state about a TCP connection.
-type tcpState struct {
- out, in TCP
- localSeqNum, remoteSeqNum *seqnum.Value
- synAck *TCP
- portPickerFD int
- finSent bool
-}
-```
-
-The next sequence numbers for each side of the connection are stored. `out` and
-`in` have defaults for the TCP header, such as the expected source and
-destination ports for outgoing packets and incoming packets.
-
-##### `layerState` interface
-
-```go
-// layerState stores the state of a layer of a connection.
-type layerState interface {
- // outgoing returns an outgoing layer to be sent in a frame.
- outgoing() Layer
-
- // incoming creates an expected Layer for comparing against a received Layer.
- // Because the expectation can depend on values in the received Layer, it is
- // an input to incoming. For example, the ACK number needs to be checked in a
- // TCP packet but only if the ACK flag is set in the received packet.
- incoming(received Layer) Layer
-
- // sent updates the layerState based on the Layer that was sent. The input is
- // a Layer with all prev and next pointers populated so that the entire frame
- // as it was sent is available.
- sent(sent Layer) error
-
- // received updates the layerState based on a Layer that is receieved. The
- // input is a Layer with all prev and next pointers populated so that the
- // entire frame as it was receieved is available.
- received(received Layer) error
-
- // close frees associated resources held by the LayerState.
- close() error
-}
-```
-
-`outgoing` generates the default Layer for an outgoing packet. For TCP, this
-would be a `TCP` with the source and destination ports populated. Because they
-are static, they are stored inside the `out` member of `tcpState`. However, the
-sequence numbers change frequently so the outgoing sequence number is stored in
-the `localSeqNum` and put into the output of outgoing for each call.
-
-`incoming` does the same functions for packets that arrive but instead of
-generating a packet to send, it generates an expect packet for filtering packets
-that arrive. For example, if a `TCP` header arrives with the wrong ports, it can
-be ignored as belonging to a different connection. `incoming` needs the received
-header itself as an input because the filter may depend on the input. For
-example, the expected sequence number depends on the flags in the TCP header.
-
-`sent` and `received` are run for each header that is actually sent or received
-and used to update the internal state. `incoming` and `outgoing` should *not* be
-used for these purpose. For example, `incoming` is called on every packet that
-arrives but only packets that match ought to actually update the state.
-`outgoing` is called to created outgoing packets and those packets are always
-sent, so unlike `incoming`/`received`, there is one `outgoing` call for each
-`sent` call.
-
-`close` cleans up after the layerState. For example, TCP and UDP need to keep a
-port reserved and then release it.
-
-#### Connections
-
-Using `layerState` above, we can create connections.
-
-```go
-// Connection holds a collection of layer states for maintaining a connection
-// along with sockets for sniffer and injecting packets.
-type Connection struct {
- layerStates []layerState
- injector Injector
- sniffer Sniffer
- t *testing.T
-}
-```
-
-The connection stores an array of `layerState` in the order that the headers
-should be present in the frame to send. For example, Ether then IPv4 then TCP.
-The injector and sniffer are for writing and reading frames. A `*testing.T` is
-stored so that internal errors can be reported directly without code in the unit
-test.
-
-The `Connection` has some useful functions:
-
-```go
-// Close frees associated resources held by the Connection.
-func (conn *Connection) Close() {...}
-// CreateFrame builds a frame for the connection with layer overriding defaults
-// of the innermost layer and additionalLayers added after it.
-func (conn *Connection) CreateFrame(layer Layer, additionalLayers ...Layer) Layers {...}
-// SendFrame sends a frame on the wire and updates the state of all layers.
-func (conn *Connection) SendFrame(frame Layers) {...}
-// Send a packet with reasonable defaults. Potentially override the final layer
-// in the connection with the provided layer and add additionLayers.
-func (conn *Connection) Send(layer Layer, additionalLayers ...Layer) {...}
-// Expect a frame with the final layerStates layer matching the provided Layer
-// within the timeout specified. If it doesn't arrive in time, it returns nil.
-func (conn *Connection) Expect(layer Layer, timeout time.Duration) (Layer, error) {...}
-// ExpectFrame expects a frame that matches the provided Layers within the
-// timeout specified. If it doesn't arrive in time, it returns nil.
-func (conn *Connection) ExpectFrame(layers Layers, timeout time.Duration) (Layers, error) {...}
-// Drain drains the sniffer's receive buffer by receiving packets until there's
-// nothing else to receive.
-func (conn *Connection) Drain() {...}
-```
-
-`CreateFrame` uses the `[]layerState` to create a frame to send. The first
-argument is for overriding defaults in the last header of the frame, because
-this is the most common need. For a TCPIPv4 connection, this would be the TCP
-header. Optional additionalLayers can be specified to add to the frame being
-created, such as a `Payload` for `TCP`.
-
-`SendFrame` sends the frame to the DUT. It is combined with `CreateFrame` to
-make `Send`. For unittests with basic sending needs, `Send` can be used. If more
-control is needed over the frame, it can be made with `CreateFrame`, modified in
-the unit test, and then sent with `SendFrame`.
-
-On the receiving side, there is `Expect` and `ExpectFrame`. Like with the
-sending side, there are two forms of each function, one for just the last header
-and one for the whole frame. The expect functions use the `[]layerState` to
-create a template for the expected incoming frame. That frame is then overridden
-by the values in the first argument. Finally, a loop starts sniffing packets on
-the wire for frames. If a matching frame is found before the timeout, it is
-returned without error. If not, nil is returned and the error contains text of
-all the received frames that didn't match. Exactly one of the outputs will be
-non-nil, even if no frames are received at all.
-
-`Drain` sniffs and discards all the frames that have yet to be received. A
-common way to write a test is:
-
-```go
-conn.Drain() // Discard all outstanding frames.
-conn.Send(...) // Send a frame with overrides.
-// Now expect a frame with a certain header and fail if it doesn't arrive.
-if _, err := conn.Expect(...); err != nil { t.Fatal(...) }
-```
-
-Or for a test where we want to check that no frame arrives:
-
-```go
-if gotOne, _ := conn.Expect(...); gotOne != nil { t.Fatal(...) }
-```
-
-#### Specializing `Connection`
-
-Because there are some common combinations of `layerState` into `Connection`,
-they are defined:
-
-```go
-// TCPIPv4 maintains the state for all the layers in a TCP/IPv4 connection.
-type TCPIPv4 Connection
-// UDPIPv4 maintains the state for all the layers in a UDP/IPv4 connection.
-type UDPIPv4 Connection
-```
-
-Each has a `NewXxx` function to create a new connection with reasonable
-defaults. They also have functions that call the underlying `Connection`
-functions but with specialization and tighter type-checking. For example:
-
-```go
-func (conn *TCPIPv4) Send(tcp TCP, additionalLayers ...Layer) {
- (*Connection)(conn).Send(&tcp, additionalLayers...)
-}
-func (conn *TCPIPv4) Drain() {
- conn.sniffer.Drain()
-}
-```
-
-They may also have some accessors to get or set the internal state of the
-connection:
-
-```go
-func (conn *TCPIPv4) state() *tcpState {
- state, ok := conn.layerStates[len(conn.layerStates)-1].(*tcpState)
- if !ok {
- conn.t.Fatalf("expected final state of %v to be tcpState", conn.layerStates)
- }
- return state
-}
-func (conn *TCPIPv4) RemoteSeqNum() *seqnum.Value {
- return conn.state().remoteSeqNum
-}
-func (conn *TCPIPv4) LocalSeqNum() *seqnum.Value {
- return conn.state().localSeqNum
-}
-```
-
-Unittests will in practice use these functions and not the functions on
-`Connection`. For example, `NewTCPIPv4()` and then call `Send` on that rather
-than cast is to a `Connection` and call `Send` on that cast result.
-
-##### Alternatives considered
-
-* Instead of storing `outgoing` and `incoming`, store values.
- * There would be many more things to store instead, like `localMac`,
- `remoteMac`, `localIP`, `remoteIP`, `localPort`, and `remotePort`.
- * Construction of a packet would be many lines to copy each of these
- values into a `[]byte`. And there would be slight variations needed for
- each encapsulation stack, like TCPIPv6 and ARP.
- * Filtering incoming packets would be a long sequence:
- * Compare the MACs, then
- * Parse the next header, then
- * Compare the IPs, then
- * Parse the next header, then
- * Compare the TCP ports. Instead it's all just one call to
- `cmp.Equal(...)`, for all sequences.
- * A TCPIPv6 connection could share most of the code. Only the type of the
- IP addresses are different. The types of `outgoing` and `incoming` would
- be remain `Layers`.
- * An ARP connection could share all the Ethernet parts. The IP `Layer`
- could be factored out of `outgoing`. After that, the IPv4 and IPv6
- connections could implement one interface and a single TCP struct could
- have either network protocol through composition.
-
-## Putting it all together
-
-Here's what te start of a packetimpact unit test looks like. This test creates a
-TCP connection with the DUT. There are added comments for explanation in this
-document but a real test might not include them in order to stay even more
-concise.
-
-```go
-func TestMyTcpTest(t *testing.T) {
- // Prepare a DUT for communication.
- dut := testbench.NewDUT(t)
-
- // This does:
- // dut.Socket()
- // dut.Bind()
- // dut.Getsockname() to learn the new port number
- // dut.Listen()
- listenFD, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(listenFD) // Tell the DUT to close the socket at the end of the test.
-
- // Monitor a new TCP connection with sniffer, injector, sequence number tracking,
- // and reasonable outgoing and incoming packet field default IPs, MACs, and port numbers.
- conn := testbench.NewTCPIPv4(t, dut, remotePort)
-
- // Perform a 3-way handshake: send SYN, expect SYNACK, send ACK.
- conn.Handshake()
-
- // Tell the DUT to accept the new connection.
- acceptFD := dut.Accept(acceptFd)
-}
-```
-
-## Other notes
-
-* The time between receiving a SYN-ACK and replying with an ACK in `Handshake`
- is about 3ms. This is much slower than the native unix response, which is
- about 0.3ms. Packetdrill gets closer to 0.3ms. For tests where timing is
- crucial, packetdrill is faster and more precise.
diff --git a/test/packetimpact/dut/BUILD b/test/packetimpact/dut/BUILD
deleted file mode 100644
index 3ce63c2c6..000000000
--- a/test/packetimpact/dut/BUILD
+++ /dev/null
@@ -1,18 +0,0 @@
-load("//tools:defs.bzl", "cc_binary", "grpcpp")
-
-package(
- default_visibility = ["//test/packetimpact:__subpackages__"],
- licenses = ["notice"],
-)
-
-cc_binary(
- name = "posix_server",
- srcs = ["posix_server.cc"],
- linkstatic = 1,
- static = True, # This is needed for running in a docker container.
- deps = [
- grpcpp,
- "//test/packetimpact/proto:posix_server_cc_grpc_proto",
- "//test/packetimpact/proto:posix_server_cc_proto",
- ],
-)
diff --git a/test/packetimpact/dut/posix_server.cc b/test/packetimpact/dut/posix_server.cc
deleted file mode 100644
index 86e580c6f..000000000
--- a/test/packetimpact/dut/posix_server.cc
+++ /dev/null
@@ -1,260 +0,0 @@
-// 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.
-
-#include <fcntl.h>
-#include <getopt.h>
-#include <netdb.h>
-#include <netinet/in.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <iostream>
-#include <unordered_map>
-
-#include "arpa/inet.h"
-#include "include/grpcpp/security/server_credentials.h"
-#include "include/grpcpp/server_builder.h"
-#include "test/packetimpact/proto/posix_server.grpc.pb.h"
-#include "test/packetimpact/proto/posix_server.pb.h"
-
-// Converts a sockaddr_storage to a Sockaddr message.
-::grpc::Status sockaddr_to_proto(const sockaddr_storage &addr,
- socklen_t addrlen,
- posix_server::Sockaddr *sockaddr_proto) {
- switch (addr.ss_family) {
- case AF_INET: {
- auto addr_in = reinterpret_cast<const sockaddr_in *>(&addr);
- auto response_in = sockaddr_proto->mutable_in();
- response_in->set_family(addr_in->sin_family);
- response_in->set_port(ntohs(addr_in->sin_port));
- response_in->mutable_addr()->assign(
- reinterpret_cast<const char *>(&addr_in->sin_addr.s_addr), 4);
- return ::grpc::Status::OK;
- }
- case AF_INET6: {
- auto addr_in6 = reinterpret_cast<const sockaddr_in6 *>(&addr);
- auto response_in6 = sockaddr_proto->mutable_in6();
- response_in6->set_family(addr_in6->sin6_family);
- response_in6->set_port(ntohs(addr_in6->sin6_port));
- response_in6->set_flowinfo(ntohl(addr_in6->sin6_flowinfo));
- response_in6->mutable_addr()->assign(
- reinterpret_cast<const char *>(&addr_in6->sin6_addr.s6_addr), 16);
- response_in6->set_scope_id(ntohl(addr_in6->sin6_scope_id));
- return ::grpc::Status::OK;
- }
- }
- return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "Unknown Sockaddr");
-}
-
-class PosixImpl final : public posix_server::Posix::Service {
- ::grpc::Status Accept(grpc_impl::ServerContext *context,
- const ::posix_server::AcceptRequest *request,
- ::posix_server::AcceptResponse *response) override {
- sockaddr_storage addr;
- socklen_t addrlen = sizeof(addr);
- response->set_fd(accept(request->sockfd(),
- reinterpret_cast<sockaddr *>(&addr), &addrlen));
- response->set_errno_(errno);
- return sockaddr_to_proto(addr, addrlen, response->mutable_addr());
- }
-
- ::grpc::Status Bind(grpc_impl::ServerContext *context,
- const ::posix_server::BindRequest *request,
- ::posix_server::BindResponse *response) override {
- if (!request->has_addr()) {
- return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
- "Missing address");
- }
- sockaddr_storage addr;
-
- switch (request->addr().sockaddr_case()) {
- case posix_server::Sockaddr::SockaddrCase::kIn: {
- auto request_in = request->addr().in();
- if (request_in.addr().size() != 4) {
- return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
- "IPv4 address must be 4 bytes");
- }
- auto addr_in = reinterpret_cast<sockaddr_in *>(&addr);
- addr_in->sin_family = request_in.family();
- addr_in->sin_port = htons(request_in.port());
- request_in.addr().copy(
- reinterpret_cast<char *>(&addr_in->sin_addr.s_addr), 4);
- break;
- }
- case posix_server::Sockaddr::SockaddrCase::kIn6: {
- auto request_in6 = request->addr().in6();
- if (request_in6.addr().size() != 16) {
- return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
- "IPv6 address must be 16 bytes");
- }
- auto addr_in6 = reinterpret_cast<sockaddr_in6 *>(&addr);
- addr_in6->sin6_family = request_in6.family();
- addr_in6->sin6_port = htons(request_in6.port());
- addr_in6->sin6_flowinfo = htonl(request_in6.flowinfo());
- request_in6.addr().copy(
- reinterpret_cast<char *>(&addr_in6->sin6_addr.s6_addr), 16);
- addr_in6->sin6_scope_id = htonl(request_in6.scope_id());
- break;
- }
- case posix_server::Sockaddr::SockaddrCase::SOCKADDR_NOT_SET:
- default:
- return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
- "Unknown Sockaddr");
- }
- response->set_ret(bind(request->sockfd(),
- reinterpret_cast<sockaddr *>(&addr), sizeof(addr)));
- response->set_errno_(errno);
- return ::grpc::Status::OK;
- }
-
- ::grpc::Status Close(grpc_impl::ServerContext *context,
- const ::posix_server::CloseRequest *request,
- ::posix_server::CloseResponse *response) override {
- response->set_ret(close(request->fd()));
- response->set_errno_(errno);
- return ::grpc::Status::OK;
- }
-
- ::grpc::Status GetSockName(
- grpc_impl::ServerContext *context,
- const ::posix_server::GetSockNameRequest *request,
- ::posix_server::GetSockNameResponse *response) override {
- sockaddr_storage addr;
- socklen_t addrlen = sizeof(addr);
- response->set_ret(getsockname(
- request->sockfd(), reinterpret_cast<sockaddr *>(&addr), &addrlen));
- response->set_errno_(errno);
- return sockaddr_to_proto(addr, addrlen, response->mutable_addr());
- }
-
- ::grpc::Status Listen(grpc_impl::ServerContext *context,
- const ::posix_server::ListenRequest *request,
- ::posix_server::ListenResponse *response) override {
- response->set_ret(listen(request->sockfd(), request->backlog()));
- response->set_errno_(errno);
- return ::grpc::Status::OK;
- }
-
- ::grpc::Status Send(::grpc::ServerContext *context,
- const ::posix_server::SendRequest *request,
- ::posix_server::SendResponse *response) override {
- response->set_ret(::send(request->sockfd(), request->buf().data(),
- request->buf().size(), request->flags()));
- response->set_errno_(errno);
- return ::grpc::Status::OK;
- }
-
- ::grpc::Status SetSockOpt(
- grpc_impl::ServerContext *context,
- const ::posix_server::SetSockOptRequest *request,
- ::posix_server::SetSockOptResponse *response) override {
- response->set_ret(setsockopt(request->sockfd(), request->level(),
- request->optname(), request->optval().c_str(),
- request->optval().size()));
- response->set_errno_(errno);
- return ::grpc::Status::OK;
- }
-
- ::grpc::Status SetSockOptInt(
- ::grpc::ServerContext *context,
- const ::posix_server::SetSockOptIntRequest *request,
- ::posix_server::SetSockOptIntResponse *response) override {
- int opt = request->intval();
- response->set_ret(::setsockopt(request->sockfd(), request->level(),
- request->optname(), &opt, sizeof(opt)));
- response->set_errno_(errno);
- return ::grpc::Status::OK;
- }
-
- ::grpc::Status SetSockOptTimeval(
- ::grpc::ServerContext *context,
- const ::posix_server::SetSockOptTimevalRequest *request,
- ::posix_server::SetSockOptTimevalResponse *response) override {
- timeval tv = {.tv_sec = static_cast<__time_t>(request->timeval().seconds()),
- .tv_usec = static_cast<__suseconds_t>(
- request->timeval().microseconds())};
- response->set_ret(setsockopt(request->sockfd(), request->level(),
- request->optname(), &tv, sizeof(tv)));
- response->set_errno_(errno);
- return ::grpc::Status::OK;
- }
-
- ::grpc::Status Socket(grpc_impl::ServerContext *context,
- const ::posix_server::SocketRequest *request,
- ::posix_server::SocketResponse *response) override {
- response->set_fd(
- socket(request->domain(), request->type(), request->protocol()));
- response->set_errno_(errno);
- return ::grpc::Status::OK;
- }
-
- ::grpc::Status Recv(::grpc::ServerContext *context,
- const ::posix_server::RecvRequest *request,
- ::posix_server::RecvResponse *response) override {
- std::vector<char> buf(request->len());
- response->set_ret(
- recv(request->sockfd(), buf.data(), buf.size(), request->flags()));
- response->set_errno_(errno);
- response->set_buf(buf.data(), response->ret());
- return ::grpc::Status::OK;
- }
-};
-
-// Parse command line options. Returns a pointer to the first argument beyond
-// the options.
-void parse_command_line_options(int argc, char *argv[], std::string *ip,
- int *port) {
- static struct option options[] = {{"ip", required_argument, NULL, 1},
- {"port", required_argument, NULL, 2},
- {0, 0, 0, 0}};
-
- // Parse the arguments.
- int c;
- while ((c = getopt_long(argc, argv, "", options, NULL)) > 0) {
- if (c == 1) {
- *ip = optarg;
- } else if (c == 2) {
- *port = std::stoi(std::string(optarg));
- }
- }
-}
-
-void run_server(const std::string &ip, int port) {
- PosixImpl posix_service;
- grpc::ServerBuilder builder;
- std::string server_address = ip + ":" + std::to_string(port);
- // Set the authentication mechanism.
- std::shared_ptr<grpc::ServerCredentials> creds =
- grpc::InsecureServerCredentials();
- builder.AddListeningPort(server_address, creds);
- builder.RegisterService(&posix_service);
-
- std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
- std::cerr << "Server listening on " << server_address << std::endl;
- server->Wait();
- std::cerr << "posix_server is finished." << std::endl;
-}
-
-int main(int argc, char *argv[]) {
- std::cerr << "posix_server is starting." << std::endl;
- std::string ip;
- int port;
- parse_command_line_options(argc, argv, &ip, &port);
-
- std::cerr << "Got IP " << ip << " and port " << port << "." << std::endl;
- run_server(ip, port);
-}
diff --git a/test/packetimpact/proto/BUILD b/test/packetimpact/proto/BUILD
deleted file mode 100644
index 4a4370f42..000000000
--- a/test/packetimpact/proto/BUILD
+++ /dev/null
@@ -1,12 +0,0 @@
-load("//tools:defs.bzl", "proto_library")
-
-package(
- default_visibility = ["//test/packetimpact:__subpackages__"],
- licenses = ["notice"],
-)
-
-proto_library(
- name = "posix_server",
- srcs = ["posix_server.proto"],
- has_services = 1,
-)
diff --git a/test/packetimpact/proto/posix_server.proto b/test/packetimpact/proto/posix_server.proto
deleted file mode 100644
index 4035e1ee6..000000000
--- a/test/packetimpact/proto/posix_server.proto
+++ /dev/null
@@ -1,193 +0,0 @@
-// 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.
-
-syntax = "proto3";
-
-package posix_server;
-
-message SockaddrIn {
- int32 family = 1;
- uint32 port = 2;
- bytes addr = 3;
-}
-
-message SockaddrIn6 {
- uint32 family = 1;
- uint32 port = 2;
- uint32 flowinfo = 3;
- bytes addr = 4;
- uint32 scope_id = 5;
-}
-
-message Sockaddr {
- oneof sockaddr {
- SockaddrIn in = 1;
- SockaddrIn6 in6 = 2;
- }
-}
-
-message Timeval {
- int64 seconds = 1;
- int64 microseconds = 2;
-}
-
-// Request and Response pairs for each Posix service RPC call, sorted.
-
-message AcceptRequest {
- int32 sockfd = 1;
-}
-
-message AcceptResponse {
- int32 fd = 1;
- int32 errno_ = 2; // "errno" may fail to compile in c++.
- Sockaddr addr = 3;
-}
-
-message BindRequest {
- int32 sockfd = 1;
- Sockaddr addr = 2;
-}
-
-message BindResponse {
- int32 ret = 1;
- int32 errno_ = 2; // "errno" may fail to compile in c++.
-}
-
-message CloseRequest {
- int32 fd = 1;
-}
-
-message CloseResponse {
- int32 ret = 1;
- int32 errno_ = 2; // "errno" may fail to compile in c++.
-}
-
-message GetSockNameRequest {
- int32 sockfd = 1;
-}
-
-message GetSockNameResponse {
- int32 ret = 1;
- int32 errno_ = 2; // "errno" may fail to compile in c++.
- Sockaddr addr = 3;
-}
-
-message ListenRequest {
- int32 sockfd = 1;
- int32 backlog = 2;
-}
-
-message ListenResponse {
- int32 ret = 1;
- int32 errno_ = 2; // "errno" may fail to compile in c++.
-}
-
-message SendRequest {
- int32 sockfd = 1;
- bytes buf = 2;
- int32 flags = 3;
-}
-
-message SendResponse {
- int32 ret = 1;
- int32 errno_ = 2;
-}
-
-message SetSockOptRequest {
- int32 sockfd = 1;
- int32 level = 2;
- int32 optname = 3;
- bytes optval = 4;
-}
-
-message SetSockOptResponse {
- int32 ret = 1;
- int32 errno_ = 2; // "errno" may fail to compile in c++.
-}
-
-message SetSockOptIntRequest {
- int32 sockfd = 1;
- int32 level = 2;
- int32 optname = 3;
- int32 intval = 4;
-}
-
-message SetSockOptIntResponse {
- int32 ret = 1;
- int32 errno_ = 2;
-}
-
-message SetSockOptTimevalRequest {
- int32 sockfd = 1;
- int32 level = 2;
- int32 optname = 3;
- Timeval timeval = 4;
-}
-
-message SetSockOptTimevalResponse {
- int32 ret = 1;
- int32 errno_ = 2; // "errno" may fail to compile in c++.
-}
-
-message SocketRequest {
- int32 domain = 1;
- int32 type = 2;
- int32 protocol = 3;
-}
-
-message SocketResponse {
- int32 fd = 1;
- int32 errno_ = 2; // "errno" may fail to compile in c++.
-}
-
-message RecvRequest {
- int32 sockfd = 1;
- int32 len = 2;
- int32 flags = 3;
-}
-
-message RecvResponse {
- int32 ret = 1;
- int32 errno_ = 2; // "errno" may fail to compile in c++.
- bytes buf = 3;
-}
-
-service Posix {
- // Call accept() on the DUT.
- rpc Accept(AcceptRequest) returns (AcceptResponse);
- // Call bind() on the DUT.
- rpc Bind(BindRequest) returns (BindResponse);
- // Call close() on the DUT.
- rpc Close(CloseRequest) returns (CloseResponse);
- // Call getsockname() on the DUT.
- rpc GetSockName(GetSockNameRequest) returns (GetSockNameResponse);
- // Call listen() on the DUT.
- rpc Listen(ListenRequest) returns (ListenResponse);
- // Call send() on the DUT.
- rpc Send(SendRequest) returns (SendResponse);
- // Call setsockopt() on the DUT. You should prefer one of the other
- // SetSockOpt* functions with a more structured optval or else you may get the
- // encoding wrong, such as making a bad assumption about the server's word
- // sizes or endianness.
- rpc SetSockOpt(SetSockOptRequest) returns (SetSockOptResponse);
- // Call setsockopt() on the DUT with an int optval.
- rpc SetSockOptInt(SetSockOptIntRequest) returns (SetSockOptIntResponse);
- // Call setsockopt() on the DUT with a Timeval optval.
- rpc SetSockOptTimeval(SetSockOptTimevalRequest)
- returns (SetSockOptTimevalResponse);
- // Call socket() on the DUT.
- rpc Socket(SocketRequest) returns (SocketResponse);
- // Call recv() on the DUT.
- rpc Recv(RecvRequest) returns (RecvResponse);
-}
diff --git a/test/packetimpact/testbench/BUILD b/test/packetimpact/testbench/BUILD
deleted file mode 100644
index 3ceceb9d7..000000000
--- a/test/packetimpact/testbench/BUILD
+++ /dev/null
@@ -1,43 +0,0 @@
-load("//tools:defs.bzl", "go_library", "go_test")
-
-package(
- default_visibility = ["//test/packetimpact:__subpackages__"],
- licenses = ["notice"],
-)
-
-go_library(
- name = "testbench",
- srcs = [
- "connections.go",
- "dut.go",
- "dut_client.go",
- "layers.go",
- "rawsockets.go",
- ],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/header",
- "//pkg/tcpip/seqnum",
- "//pkg/usermem",
- "//test/packetimpact/proto:posix_server_go_proto",
- "@com_github_google_go-cmp//cmp:go_default_library",
- "@com_github_google_go-cmp//cmp/cmpopts:go_default_library",
- "@com_github_mohae_deepcopy//:go_default_library",
- "@org_golang_google_grpc//:go_default_library",
- "@org_golang_google_grpc//keepalive:go_default_library",
- "@org_golang_x_sys//unix:go_default_library",
- "@org_uber_go_multierr//:go_default_library",
- ],
-)
-
-go_test(
- name = "testbench_test",
- size = "small",
- srcs = ["layers_test.go"],
- library = ":testbench",
- deps = [
- "//pkg/tcpip",
- "@com_github_mohae_deepcopy//:go_default_library",
- ],
-)
diff --git a/test/packetimpact/testbench/connections.go b/test/packetimpact/testbench/connections.go
deleted file mode 100644
index 2280bd4ee..000000000
--- a/test/packetimpact/testbench/connections.go
+++ /dev/null
@@ -1,824 +0,0 @@
-// 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 has utilities to send and receive packets and also command
-// the DUT to run POSIX functions.
-package testbench
-
-import (
- "flag"
- "fmt"
- "math/rand"
- "net"
- "testing"
- "time"
-
- "github.com/mohae/deepcopy"
- "go.uber.org/multierr"
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/seqnum"
-)
-
-var localIPv4 = flag.String("local_ipv4", "", "local IPv4 address for test packets")
-var remoteIPv4 = flag.String("remote_ipv4", "", "remote IPv4 address for test packets")
-var localIPv6 = flag.String("local_ipv6", "", "local IPv6 address for test packets")
-var remoteIPv6 = flag.String("remote_ipv6", "", "remote IPv6 address for test packets")
-var localMAC = flag.String("local_mac", "", "local mac address for test packets")
-var remoteMAC = flag.String("remote_mac", "", "remote mac address for test packets")
-
-// pickPort makes a new socket and returns the socket FD and port. The domain
-// should be AF_INET or AF_INET6. The caller must close the FD when done with
-// the port if there is no error.
-func pickPort(domain, typ int) (fd int, port uint16, err error) {
- fd, err = unix.Socket(domain, typ, 0)
- if err != nil {
- return -1, 0, err
- }
- defer func() {
- if err != nil {
- err = multierr.Append(err, unix.Close(fd))
- }
- }()
- var sa unix.Sockaddr
- switch domain {
- case unix.AF_INET:
- var sa4 unix.SockaddrInet4
- copy(sa4.Addr[:], net.ParseIP(*localIPv4).To4())
- sa = &sa4
- case unix.AF_INET6:
- var sa6 unix.SockaddrInet6
- copy(sa6.Addr[:], net.ParseIP(*localIPv6).To16())
- sa = &sa6
- default:
- return -1, 0, fmt.Errorf("invalid domain %d, it should be one of unix.AF_INET or unix.AF_INET6", domain)
- }
- if err = unix.Bind(fd, sa); err != nil {
- return -1, 0, err
- }
- newSockAddr, err := unix.Getsockname(fd)
- if err != nil {
- return -1, 0, err
- }
- switch domain {
- case unix.AF_INET:
- newSockAddrInet4, ok := newSockAddr.(*unix.SockaddrInet4)
- if !ok {
- return -1, 0, fmt.Errorf("can't cast Getsockname result %T to SockaddrInet4", newSockAddr)
- }
- return fd, uint16(newSockAddrInet4.Port), nil
- case unix.AF_INET6:
- newSockAddrInet6, ok := newSockAddr.(*unix.SockaddrInet6)
- if !ok {
- return -1, 0, fmt.Errorf("can't cast Getsockname result %T to SockaddrInet6", newSockAddr)
- }
- return fd, uint16(newSockAddrInet6.Port), nil
- default:
- return -1, 0, fmt.Errorf("invalid domain %d, it should be one of unix.AF_INET or unix.AF_INET6", domain)
- }
-}
-
-// layerState stores the state of a layer of a connection.
-type layerState interface {
- // outgoing returns an outgoing layer to be sent in a frame. It should not
- // update layerState, that is done in layerState.sent.
- outgoing() Layer
-
- // incoming creates an expected Layer for comparing against a received Layer.
- // Because the expectation can depend on values in the received Layer, it is
- // an input to incoming. For example, the ACK number needs to be checked in a
- // TCP packet but only if the ACK flag is set in the received packet. It
- // should not update layerState, that is done in layerState.received. The
- // caller takes ownership of the returned Layer.
- incoming(received Layer) Layer
-
- // sent updates the layerState based on the Layer that was sent. The input is
- // a Layer with all prev and next pointers populated so that the entire frame
- // as it was sent is available.
- sent(sent Layer) error
-
- // received updates the layerState based on a Layer that is receieved. The
- // input is a Layer with all prev and next pointers populated so that the
- // entire frame as it was receieved is available.
- received(received Layer) error
-
- // close frees associated resources held by the LayerState.
- close() error
-}
-
-// etherState maintains state about an Ethernet connection.
-type etherState struct {
- out, in Ether
-}
-
-var _ layerState = (*etherState)(nil)
-
-// newEtherState creates a new etherState.
-func newEtherState(out, in Ether) (*etherState, error) {
- lMAC, err := tcpip.ParseMACAddress(*localMAC)
- if err != nil {
- return nil, err
- }
-
- rMAC, err := tcpip.ParseMACAddress(*remoteMAC)
- if err != nil {
- return nil, err
- }
- s := etherState{
- out: Ether{SrcAddr: &lMAC, DstAddr: &rMAC},
- in: Ether{SrcAddr: &rMAC, DstAddr: &lMAC},
- }
- if err := s.out.merge(&out); err != nil {
- return nil, err
- }
- if err := s.in.merge(&in); err != nil {
- return nil, err
- }
- return &s, nil
-}
-
-func (s *etherState) outgoing() Layer {
- return deepcopy.Copy(&s.out).(Layer)
-}
-
-// incoming implements layerState.incoming.
-func (s *etherState) incoming(Layer) Layer {
- return deepcopy.Copy(&s.in).(Layer)
-}
-
-func (*etherState) sent(Layer) error {
- return nil
-}
-
-func (*etherState) received(Layer) error {
- return nil
-}
-
-func (*etherState) close() error {
- return nil
-}
-
-// ipv4State maintains state about an IPv4 connection.
-type ipv4State struct {
- out, in IPv4
-}
-
-var _ layerState = (*ipv4State)(nil)
-
-// newIPv4State creates a new ipv4State.
-func newIPv4State(out, in IPv4) (*ipv4State, error) {
- lIP := tcpip.Address(net.ParseIP(*localIPv4).To4())
- rIP := tcpip.Address(net.ParseIP(*remoteIPv4).To4())
- s := ipv4State{
- out: IPv4{SrcAddr: &lIP, DstAddr: &rIP},
- in: IPv4{SrcAddr: &rIP, DstAddr: &lIP},
- }
- if err := s.out.merge(&out); err != nil {
- return nil, err
- }
- if err := s.in.merge(&in); err != nil {
- return nil, err
- }
- return &s, nil
-}
-
-func (s *ipv4State) outgoing() Layer {
- return deepcopy.Copy(&s.out).(Layer)
-}
-
-// incoming implements layerState.incoming.
-func (s *ipv4State) incoming(Layer) Layer {
- return deepcopy.Copy(&s.in).(Layer)
-}
-
-func (*ipv4State) sent(Layer) error {
- return nil
-}
-
-func (*ipv4State) received(Layer) error {
- return nil
-}
-
-func (*ipv4State) close() error {
- return nil
-}
-
-// ipv6State maintains state about an IPv6 connection.
-type ipv6State struct {
- out, in IPv6
-}
-
-var _ layerState = (*ipv6State)(nil)
-
-// newIPv6State creates a new ipv6State.
-func newIPv6State(out, in IPv6) (*ipv6State, error) {
- lIP := tcpip.Address(net.ParseIP(*localIPv6).To16())
- rIP := tcpip.Address(net.ParseIP(*remoteIPv6).To16())
- s := ipv6State{
- out: IPv6{SrcAddr: &lIP, DstAddr: &rIP},
- in: IPv6{SrcAddr: &rIP, DstAddr: &lIP},
- }
- if err := s.out.merge(&out); err != nil {
- return nil, err
- }
- if err := s.in.merge(&in); err != nil {
- return nil, err
- }
- return &s, nil
-}
-
-// outgoing returns an outgoing layer to be sent in a frame.
-func (s *ipv6State) outgoing() Layer {
- return deepcopy.Copy(&s.out).(Layer)
-}
-
-func (s *ipv6State) incoming(Layer) Layer {
- return deepcopy.Copy(&s.in).(Layer)
-}
-
-func (s *ipv6State) sent(Layer) error {
- // Nothing to do.
- return nil
-}
-
-func (s *ipv6State) received(Layer) error {
- // Nothing to do.
- return nil
-}
-
-// close cleans up any resources held.
-func (s *ipv6State) close() error {
- return nil
-}
-
-// tcpState maintains state about a TCP connection.
-type tcpState struct {
- out, in TCP
- localSeqNum, remoteSeqNum *seqnum.Value
- synAck *TCP
- portPickerFD int
- finSent bool
-}
-
-var _ layerState = (*tcpState)(nil)
-
-// SeqNumValue is a helper routine that allocates a new seqnum.Value value to
-// store v and returns a pointer to it.
-func SeqNumValue(v seqnum.Value) *seqnum.Value {
- return &v
-}
-
-// newTCPState creates a new TCPState.
-func newTCPState(domain int, out, in TCP) (*tcpState, error) {
- portPickerFD, localPort, err := pickPort(domain, unix.SOCK_STREAM)
- if err != nil {
- return nil, err
- }
- s := tcpState{
- out: TCP{SrcPort: &localPort},
- in: TCP{DstPort: &localPort},
- localSeqNum: SeqNumValue(seqnum.Value(rand.Uint32())),
- portPickerFD: portPickerFD,
- finSent: false,
- }
- if err := s.out.merge(&out); err != nil {
- return nil, err
- }
- if err := s.in.merge(&in); err != nil {
- return nil, err
- }
- return &s, nil
-}
-
-func (s *tcpState) outgoing() Layer {
- newOutgoing := deepcopy.Copy(s.out).(TCP)
- if s.localSeqNum != nil {
- newOutgoing.SeqNum = Uint32(uint32(*s.localSeqNum))
- }
- if s.remoteSeqNum != nil {
- newOutgoing.AckNum = Uint32(uint32(*s.remoteSeqNum))
- }
- return &newOutgoing
-}
-
-// incoming implements layerState.incoming.
-func (s *tcpState) incoming(received Layer) Layer {
- tcpReceived, ok := received.(*TCP)
- if !ok {
- return nil
- }
- newIn := deepcopy.Copy(s.in).(TCP)
- if s.remoteSeqNum != nil {
- newIn.SeqNum = Uint32(uint32(*s.remoteSeqNum))
- }
- if s.localSeqNum != nil && (*tcpReceived.Flags&header.TCPFlagAck) != 0 {
- // The caller didn't specify an AckNum so we'll expect the calculated one,
- // but only if the ACK flag is set because the AckNum is not valid in a
- // header if ACK is not set.
- newIn.AckNum = Uint32(uint32(*s.localSeqNum))
- }
- return &newIn
-}
-
-func (s *tcpState) sent(sent Layer) error {
- tcp, ok := sent.(*TCP)
- if !ok {
- return fmt.Errorf("can't update tcpState with %T Layer", sent)
- }
- if !s.finSent {
- // update localSeqNum by the payload only when FIN is not yet sent by us
- for current := tcp.next(); current != nil; current = current.next() {
- s.localSeqNum.UpdateForward(seqnum.Size(current.length()))
- }
- }
- if tcp.Flags != nil && *tcp.Flags&(header.TCPFlagSyn|header.TCPFlagFin) != 0 {
- s.localSeqNum.UpdateForward(1)
- }
- if *tcp.Flags&(header.TCPFlagFin) != 0 {
- s.finSent = true
- }
- return nil
-}
-
-func (s *tcpState) received(l Layer) error {
- tcp, ok := l.(*TCP)
- if !ok {
- return fmt.Errorf("can't update tcpState with %T Layer", l)
- }
- s.remoteSeqNum = SeqNumValue(seqnum.Value(*tcp.SeqNum))
- if *tcp.Flags&(header.TCPFlagSyn|header.TCPFlagFin) != 0 {
- s.remoteSeqNum.UpdateForward(1)
- }
- for current := tcp.next(); current != nil; current = current.next() {
- s.remoteSeqNum.UpdateForward(seqnum.Size(current.length()))
- }
- return nil
-}
-
-// close frees the port associated with this connection.
-func (s *tcpState) close() error {
- if err := unix.Close(s.portPickerFD); err != nil {
- return err
- }
- s.portPickerFD = -1
- return nil
-}
-
-// udpState maintains state about a UDP connection.
-type udpState struct {
- out, in UDP
- portPickerFD int
-}
-
-var _ layerState = (*udpState)(nil)
-
-// newUDPState creates a new udpState.
-func newUDPState(domain int, out, in UDP) (*udpState, error) {
- portPickerFD, localPort, err := pickPort(domain, unix.SOCK_DGRAM)
- if err != nil {
- return nil, err
- }
- s := udpState{
- out: UDP{SrcPort: &localPort},
- in: UDP{DstPort: &localPort},
- portPickerFD: portPickerFD,
- }
- if err := s.out.merge(&out); err != nil {
- return nil, err
- }
- if err := s.in.merge(&in); err != nil {
- return nil, err
- }
- return &s, nil
-}
-
-func (s *udpState) outgoing() Layer {
- return deepcopy.Copy(&s.out).(Layer)
-}
-
-// incoming implements layerState.incoming.
-func (s *udpState) incoming(Layer) Layer {
- return deepcopy.Copy(&s.in).(Layer)
-}
-
-func (*udpState) sent(l Layer) error {
- return nil
-}
-
-func (*udpState) received(l Layer) error {
- return nil
-}
-
-// close frees the port associated with this connection.
-func (s *udpState) close() error {
- if err := unix.Close(s.portPickerFD); err != nil {
- return err
- }
- s.portPickerFD = -1
- return nil
-}
-
-// Connection holds a collection of layer states for maintaining a connection
-// along with sockets for sniffer and injecting packets.
-type Connection struct {
- layerStates []layerState
- injector Injector
- sniffer Sniffer
- t *testing.T
-}
-
-// Returns the default incoming frame against which to match. If received is
-// longer than layerStates then that may still count as a match. The reverse is
-// never a match and nil is returned.
-func (conn *Connection) incoming(received Layers) Layers {
- if len(received) < len(conn.layerStates) {
- return nil
- }
- in := Layers{}
- for i, s := range conn.layerStates {
- toMatch := s.incoming(received[i])
- if toMatch == nil {
- return nil
- }
- in = append(in, toMatch)
- }
- return in
-}
-
-func (conn *Connection) match(override, received Layers) bool {
- toMatch := conn.incoming(received)
- if toMatch == nil {
- return false // Not enough layers in gotLayers for matching.
- }
- if err := toMatch.merge(override); err != nil {
- return false // Failing to merge is not matching.
- }
- return toMatch.match(received)
-}
-
-// Close frees associated resources held by the Connection.
-func (conn *Connection) Close() {
- errs := multierr.Combine(conn.sniffer.close(), conn.injector.close())
- for _, s := range conn.layerStates {
- if err := s.close(); err != nil {
- errs = multierr.Append(errs, fmt.Errorf("unable to close %+v: %s", s, err))
- }
- }
- if errs != nil {
- conn.t.Fatalf("unable to close %+v: %s", conn, errs)
- }
-}
-
-// CreateFrame builds a frame for the connection with layer overriding defaults
-// of the innermost layer and additionalLayers added after it.
-func (conn *Connection) CreateFrame(layer Layer, additionalLayers ...Layer) Layers {
- var layersToSend Layers
- for _, s := range conn.layerStates {
- layersToSend = append(layersToSend, s.outgoing())
- }
- if err := layersToSend[len(layersToSend)-1].merge(layer); err != nil {
- conn.t.Fatalf("can't merge %+v into %+v: %s", layer, layersToSend[len(layersToSend)-1], err)
- }
- layersToSend = append(layersToSend, additionalLayers...)
- return layersToSend
-}
-
-// SendFrame sends a frame on the wire and updates the state of all layers.
-func (conn *Connection) 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)
-
- // frame might have nil values where the caller wanted to use default values.
- // sentFrame will have no nil values in it because it comes from parsing the
- // bytes that were actually sent.
- sentFrame := parse(parseEther, outBytes)
- // Update the state of each layer based on what was sent.
- for i, s := range conn.layerStates {
- if err := s.sent(sentFrame[i]); err != nil {
- conn.t.Fatalf("Unable to update the state of %+v with %s: %s", s, sentFrame[i], err)
- }
- }
-}
-
-// Send a packet with reasonable defaults. Potentially override the final layer
-// in the connection with the provided layer and add additionLayers.
-func (conn *Connection) Send(layer Layer, additionalLayers ...Layer) {
- conn.SendFrame(conn.CreateFrame(layer, additionalLayers...))
-}
-
-// recvFrame gets the next successfully parsed frame (of type Layers) within the
-// timeout provided. If no parsable frame arrives before the timeout, it returns
-// nil.
-func (conn *Connection) recvFrame(timeout time.Duration) Layers {
- if timeout <= 0 {
- return nil
- }
- b := conn.sniffer.Recv(timeout)
- if b == nil {
- return nil
- }
- return parse(parseEther, b)
-}
-
-// layersError stores the Layers that we got and the Layers that we wanted to
-// match.
-type layersError struct {
- got, want Layers
-}
-
-func (e *layersError) Error() string {
- return e.got.diff(e.want)
-}
-
-// Expect a frame with the final layerStates layer matching the provided Layer
-// within the timeout specified. If it doesn't arrive in time, it returns nil.
-func (conn *Connection) Expect(layer Layer, timeout time.Duration) (Layer, error) {
- // Make a frame that will ignore all but the final layer.
- layers := make([]Layer, len(conn.layerStates))
- layers[len(layers)-1] = layer
-
- gotFrame, err := conn.ExpectFrame(layers, timeout)
- if err != nil {
- return nil, err
- }
- if len(conn.layerStates)-1 < len(gotFrame) {
- return gotFrame[len(conn.layerStates)-1], nil
- }
- conn.t.Fatal("the received frame should be at least as long as the expected layers")
- panic("unreachable")
-}
-
-// ExpectFrame expects a frame that matches the provided Layers within the
-// timeout specified. If one arrives in time, the Layers is returned without an
-// error. If it doesn't arrive in time, it returns nil and error is non-nil.
-func (conn *Connection) ExpectFrame(layers Layers, timeout time.Duration) (Layers, error) {
- deadline := time.Now().Add(timeout)
- var errs error
- for {
- var gotLayers Layers
- if timeout = time.Until(deadline); timeout > 0 {
- gotLayers = conn.recvFrame(timeout)
- }
- if gotLayers == nil {
- if errs == nil {
- return nil, fmt.Errorf("got no frames matching %v during %s", layers, timeout)
- }
- return nil, fmt.Errorf("got no frames matching %v during %s: got %w", layers, timeout, errs)
- }
- if conn.match(layers, gotLayers) {
- for i, s := range conn.layerStates {
- if err := s.received(gotLayers[i]); err != nil {
- conn.t.Fatal(err)
- }
- }
- return gotLayers, nil
- }
- errs = multierr.Combine(errs, &layersError{got: gotLayers, want: conn.incoming(gotLayers)})
- }
-}
-
-// Drain drains the sniffer's receive buffer by receiving packets until there's
-// nothing else to receive.
-func (conn *Connection) Drain() {
- conn.sniffer.Drain()
-}
-
-// TCPIPv4 maintains the state for all the layers in a TCP/IPv4 connection.
-type TCPIPv4 Connection
-
-// NewTCPIPv4 creates a new TCPIPv4 connection with reasonable defaults.
-func NewTCPIPv4(t *testing.T, outgoingTCP, incomingTCP TCP) TCPIPv4 {
- etherState, err := newEtherState(Ether{}, Ether{})
- if err != nil {
- t.Fatalf("can't make etherState: %s", err)
- }
- ipv4State, err := newIPv4State(IPv4{}, IPv4{})
- if err != nil {
- t.Fatalf("can't make ipv4State: %s", err)
- }
- tcpState, err := newTCPState(unix.AF_INET, outgoingTCP, incomingTCP)
- if err != nil {
- t.Fatalf("can't make tcpState: %s", err)
- }
- injector, err := NewInjector(t)
- if err != nil {
- t.Fatalf("can't make injector: %s", err)
- }
- sniffer, err := NewSniffer(t)
- if err != nil {
- t.Fatalf("can't make sniffer: %s", err)
- }
-
- return TCPIPv4{
- layerStates: []layerState{etherState, ipv4State, tcpState},
- injector: injector,
- sniffer: sniffer,
- t: t,
- }
-}
-
-// Handshake performs a TCP 3-way handshake. The input Connection should have a
-// final TCP Layer.
-func (conn *TCPIPv4) Handshake() {
- // Send the SYN.
- conn.Send(TCP{Flags: Uint8(header.TCPFlagSyn)})
-
- // Wait for the SYN-ACK.
- 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.layerStates[len(conn.layerStates)-1].(*tcpState).synAck = synAck
-
- // Send an ACK.
- conn.Send(TCP{Flags: Uint8(header.TCPFlagAck)})
-}
-
-// ExpectData is a convenient method that expects a Layer and the Layer after
-// it. If it doens't arrive in time, it returns nil.
-func (conn *TCPIPv4) ExpectData(tcp *TCP, payload *Payload, timeout time.Duration) (Layers, error) {
- expected := make([]Layer, len(conn.layerStates))
- expected[len(expected)-1] = tcp
- if payload != nil {
- expected = append(expected, payload)
- }
- return (*Connection)(conn).ExpectFrame(expected, timeout)
-}
-
-// Send a packet with reasonable defaults. Potentially override the TCP layer in
-// the connection with the provided layer and add additionLayers.
-func (conn *TCPIPv4) Send(tcp TCP, additionalLayers ...Layer) {
- (*Connection)(conn).Send(&tcp, additionalLayers...)
-}
-
-// Close frees associated resources held by the TCPIPv4 connection.
-func (conn *TCPIPv4) Close() {
- (*Connection)(conn).Close()
-}
-
-// Expect a frame with the TCP layer matching the provided TCP within the
-// timeout specified. If it doesn't arrive in time, it returns nil.
-func (conn *TCPIPv4) Expect(tcp TCP, timeout time.Duration) (*TCP, error) {
- layer, err := (*Connection)(conn).Expect(&tcp, timeout)
- if layer == nil {
- return nil, err
- }
- gotTCP, ok := layer.(*TCP)
- if !ok {
- conn.t.Fatalf("expected %s to be TCP", layer)
- }
- return gotTCP, err
-}
-
-func (conn *TCPIPv4) state() *tcpState {
- state, ok := conn.layerStates[len(conn.layerStates)-1].(*tcpState)
- if !ok {
- conn.t.Fatalf("expected final state of %v to be tcpState", conn.layerStates)
- }
- return state
-}
-
-// RemoteSeqNum returns the next expected sequence number from the DUT.
-func (conn *TCPIPv4) RemoteSeqNum() *seqnum.Value {
- return conn.state().remoteSeqNum
-}
-
-// LocalSeqNum returns the next sequence number to send from the testbench.
-func (conn *TCPIPv4) LocalSeqNum() *seqnum.Value {
- return conn.state().localSeqNum
-}
-
-// SynAck returns the SynAck that was part of the handshake.
-func (conn *TCPIPv4) SynAck() *TCP {
- return conn.state().synAck
-}
-
-// IPv6Conn maintains the state for all the layers in a IPv6 connection.
-type IPv6Conn Connection
-
-// NewIPv6Conn creates a new IPv6Conn connection with reasonable defaults.
-func NewIPv6Conn(t *testing.T, outgoingIPv6, incomingIPv6 IPv6) IPv6Conn {
- etherState, err := newEtherState(Ether{}, Ether{})
- if err != nil {
- t.Fatalf("can't make EtherState: %s", err)
- }
- ipv6State, err := newIPv6State(outgoingIPv6, incomingIPv6)
- if err != nil {
- t.Fatalf("can't make IPv6State: %s", err)
- }
-
- injector, err := NewInjector(t)
- if err != nil {
- t.Fatalf("can't make injector: %s", err)
- }
- sniffer, err := NewSniffer(t)
- if err != nil {
- t.Fatalf("can't make sniffer: %s", err)
- }
-
- return IPv6Conn{
- layerStates: []layerState{etherState, ipv6State},
- injector: injector,
- sniffer: sniffer,
- t: t,
- }
-}
-
-// SendFrame sends a frame on the wire and updates the state of all layers.
-func (conn *IPv6Conn) SendFrame(frame Layers) {
- (*Connection)(conn).SendFrame(frame)
-}
-
-// CreateFrame builds a frame for the connection with ipv6 overriding the ipv6
-// layer defaults and additionalLayers added after it.
-func (conn *IPv6Conn) CreateFrame(ipv6 IPv6, additionalLayers ...Layer) Layers {
- return (*Connection)(conn).CreateFrame(&ipv6, additionalLayers...)
-}
-
-// Close to clean up any resources held.
-func (conn *IPv6Conn) Close() {
- (*Connection)(conn).Close()
-}
-
-// ExpectFrame expects a frame that matches the provided Layers within the
-// timeout specified. If it doesn't arrive in time, it returns nil.
-func (conn *IPv6Conn) ExpectFrame(frame Layers, timeout time.Duration) (Layers, error) {
- return (*Connection)(conn).ExpectFrame(frame, timeout)
-}
-
-// Drain drains the sniffer's receive buffer by receiving packets until there's
-// nothing else to receive.
-func (conn *TCPIPv4) Drain() {
- conn.sniffer.Drain()
-}
-
-// UDPIPv4 maintains the state for all the layers in a UDP/IPv4 connection.
-type UDPIPv4 Connection
-
-// NewUDPIPv4 creates a new UDPIPv4 connection with reasonable defaults.
-func NewUDPIPv4(t *testing.T, outgoingUDP, incomingUDP UDP) UDPIPv4 {
- etherState, err := newEtherState(Ether{}, Ether{})
- if err != nil {
- t.Fatalf("can't make etherState: %s", err)
- }
- ipv4State, err := newIPv4State(IPv4{}, IPv4{})
- if err != nil {
- t.Fatalf("can't make ipv4State: %s", err)
- }
- tcpState, err := newUDPState(unix.AF_INET, outgoingUDP, incomingUDP)
- if err != nil {
- t.Fatalf("can't make udpState: %s", err)
- }
- injector, err := NewInjector(t)
- if err != nil {
- t.Fatalf("can't make injector: %s", err)
- }
- sniffer, err := NewSniffer(t)
- if err != nil {
- t.Fatalf("can't make sniffer: %s", err)
- }
-
- return UDPIPv4{
- layerStates: []layerState{etherState, ipv4State, tcpState},
- injector: injector,
- sniffer: sniffer,
- t: t,
- }
-}
-
-// CreateFrame builds a frame for the connection with layer overriding defaults
-// of the innermost layer and additionalLayers added after it.
-func (conn *UDPIPv4) CreateFrame(layer Layer, additionalLayers ...Layer) Layers {
- return (*Connection)(conn).CreateFrame(layer, additionalLayers...)
-}
-
-// SendFrame sends a frame on the wire and updates the state of all layers.
-func (conn *UDPIPv4) SendFrame(frame Layers) {
- (*Connection)(conn).SendFrame(frame)
-}
-
-// Close frees associated resources held by the UDPIPv4 connection.
-func (conn *UDPIPv4) Close() {
- (*Connection)(conn).Close()
-}
-
-// Drain drains the sniffer's receive buffer by receiving packets until there's
-// nothing else to receive.
-func (conn *UDPIPv4) Drain() {
- conn.sniffer.Drain()
-}
diff --git a/test/packetimpact/testbench/dut.go b/test/packetimpact/testbench/dut.go
deleted file mode 100644
index 3f340c6bc..000000000
--- a/test/packetimpact/testbench/dut.go
+++ /dev/null
@@ -1,473 +0,0 @@
-// 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 (
- "context"
- "flag"
- "net"
- "strconv"
- "syscall"
- "testing"
- "time"
-
- pb "gvisor.dev/gvisor/test/packetimpact/proto/posix_server_go_proto"
-
- "golang.org/x/sys/unix"
- "google.golang.org/grpc"
- "google.golang.org/grpc/keepalive"
-)
-
-var (
- posixServerIP = flag.String("posix_server_ip", "", "ip address to listen to for UDP commands")
- posixServerPort = flag.Int("posix_server_port", 40000, "port to listen to for UDP commands")
- rpcTimeout = flag.Duration("rpc_timeout", 100*time.Millisecond, "gRPC timeout")
- rpcKeepalive = flag.Duration("rpc_keepalive", 10*time.Second, "gRPC keepalive")
-)
-
-// DUT communicates with the DUT to force it to make POSIX calls.
-type DUT struct {
- t *testing.T
- conn *grpc.ClientConn
- posixServer PosixClient
-}
-
-// NewDUT creates a new connection with the DUT over gRPC.
-func NewDUT(t *testing.T) DUT {
- flag.Parse()
- posixServerAddress := *posixServerIP + ":" + strconv.Itoa(*posixServerPort)
- conn, err := grpc.Dial(posixServerAddress, grpc.WithInsecure(), grpc.WithKeepaliveParams(keepalive.ClientParameters{Timeout: *rpcKeepalive}))
- if err != nil {
- t.Fatalf("failed to grpc.Dial(%s): %s", posixServerAddress, err)
- }
- posixServer := NewPosixClient(conn)
- return DUT{
- t: t,
- conn: conn,
- posixServer: posixServer,
- }
-}
-
-// TearDown closes the underlying connection.
-func (dut *DUT) TearDown() {
- dut.conn.Close()
-}
-
-func (dut *DUT) sockaddrToProto(sa unix.Sockaddr) *pb.Sockaddr {
- dut.t.Helper()
- switch s := sa.(type) {
- case *unix.SockaddrInet4:
- return &pb.Sockaddr{
- Sockaddr: &pb.Sockaddr_In{
- In: &pb.SockaddrIn{
- Family: unix.AF_INET,
- Port: uint32(s.Port),
- Addr: s.Addr[:],
- },
- },
- }
- case *unix.SockaddrInet6:
- return &pb.Sockaddr{
- Sockaddr: &pb.Sockaddr_In6{
- In6: &pb.SockaddrIn6{
- Family: unix.AF_INET6,
- Port: uint32(s.Port),
- Flowinfo: 0,
- ScopeId: s.ZoneId,
- Addr: s.Addr[:],
- },
- },
- }
- }
- dut.t.Fatalf("can't parse Sockaddr: %+v", sa)
- return nil
-}
-
-func (dut *DUT) protoToSockaddr(sa *pb.Sockaddr) unix.Sockaddr {
- dut.t.Helper()
- switch s := sa.Sockaddr.(type) {
- case *pb.Sockaddr_In:
- ret := unix.SockaddrInet4{
- Port: int(s.In.GetPort()),
- }
- copy(ret.Addr[:], s.In.GetAddr())
- return &ret
- case *pb.Sockaddr_In6:
- ret := unix.SockaddrInet6{
- Port: int(s.In6.GetPort()),
- ZoneId: s.In6.GetScopeId(),
- }
- copy(ret.Addr[:], s.In6.GetAddr())
- }
- dut.t.Fatalf("can't parse Sockaddr: %+v", sa)
- 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.Fatalf("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(ctx context.Context, fd int32, sa unix.Sockaddr) (int32, error) {
- dut.t.Helper()
- req := pb.BindRequest{
- Sockfd: fd,
- Addr: dut.sockaddrToProto(sa),
- }
- resp, err := dut.posixServer.Bind(ctx, &req)
- if err != nil {
- dut.t.Fatalf("failed to call Bind: %s", err)
- }
- return resp.GetRet(), syscall.Errno(resp.GetErrno_())
-}
-
-// 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()
- ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout)
- defer cancel()
- ret, err := dut.CloseWithErrno(ctx, fd)
- if ret != 0 {
- 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(ctx context.Context, sockfd int32) (int32, unix.Sockaddr, error) {
- dut.t.Helper()
- req := pb.GetSockNameRequest{
- Sockfd: sockfd,
- }
- resp, err := dut.posixServer.GetSockName(ctx, &req)
- if err != nil {
- dut.t.Fatalf("failed to call Bind: %s", err)
- }
- return resp.GetRet(), dut.protoToSockaddr(resp.GetAddr()), syscall.Errno(resp.GetErrno_())
-}
-
-// 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()
- ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout)
- defer cancel()
- ret, err := dut.ListenWithErrno(ctx, sockfd, backlog)
- if ret != 0 {
- dut.t.Fatalf("failed to listen: %s", err)
- }
-}
-
-// ListenWithErrno calls listen on the DUT.
-func (dut *DUT) ListenWithErrno(ctx context.Context, sockfd, backlog int32) (int32, error) {
- dut.t.Helper()
- req := pb.ListenRequest{
- Sockfd: sockfd,
- Backlog: backlog,
- }
- resp, err := dut.posixServer.Listen(ctx, &req)
- if err != nil {
- dut.t.Fatalf("failed to call Listen: %s", err)
- }
- return resp.GetRet(), syscall.Errno(resp.GetErrno_())
-}
-
-// 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()
- 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
-}
-
-// 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.SendRequest{
- Sockfd: sockfd,
- Buf: buf,
- Flags: flags,
- }
- resp, err := dut.posixServer.Send(ctx, &req)
- if err != nil {
- dut.t.Fatalf("failed to call Send: %s", err)
- }
- return resp.GetRet(), syscall.Errno(resp.GetErrno_())
-}
-
-// 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()
- 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)
- }
-}
-
-// 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,
- Level: level,
- Optname: optname,
- Optval: optval,
- }
- resp, err := dut.posixServer.SetSockOpt(ctx, &req)
- if err != nil {
- dut.t.Fatalf("failed to call SetSockOpt: %s", err)
- }
- return resp.GetRet(), syscall.Errno(resp.GetErrno_())
-}
-
-// 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()
- 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 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(ctx context.Context, sockfd, level, optname int32, tv *unix.Timeval) (int32, error) {
- dut.t.Helper()
- timeval := pb.Timeval{
- Seconds: int64(tv.Sec),
- Microseconds: int64(tv.Usec),
- }
- req := pb.SetSockOptTimevalRequest{
- Sockfd: sockfd,
- Level: level,
- Optname: optname,
- Timeval: &timeval,
- }
- resp, err := dut.posixServer.SetSockOptTimeval(ctx, &req)
- if err != nil {
- dut.t.Fatalf("failed to call SetSockOptTimeval: %s", err)
- }
- return resp.GetRet(), 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
-}
-
-// 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_())
-}
-
-// 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()
- 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
-}
-
-// RecvWithErrno calls recv on the DUT.
-func (dut *DUT) RecvWithErrno(ctx context.Context, sockfd, len, flags int32) (int32, []byte, error) {
- dut.t.Helper()
- req := pb.RecvRequest{
- Sockfd: sockfd,
- Len: len,
- Flags: flags,
- }
- resp, err := dut.posixServer.Recv(ctx, &req)
- if err != nil {
- dut.t.Fatalf("failed to call Recv: %s", err)
- }
- return resp.GetRet(), resp.GetBuf(), syscall.Errno(resp.GetErrno_())
-}
diff --git a/test/packetimpact/testbench/dut_client.go b/test/packetimpact/testbench/dut_client.go
deleted file mode 100644
index b130a33a2..000000000
--- a/test/packetimpact/testbench/dut_client.go
+++ /dev/null
@@ -1,28 +0,0 @@
-// 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 (
- "google.golang.org/grpc"
- pb "gvisor.dev/gvisor/test/packetimpact/proto/posix_server_go_proto"
-)
-
-// PosixClient is a gRPC client for the Posix service.
-type PosixClient pb.PosixClient
-
-// NewPosixClient makes a new gRPC client for the Posix service.
-func NewPosixClient(c grpc.ClientConnInterface) PosixClient {
- return pb.NewPosixClient(c)
-}
diff --git a/test/packetimpact/testbench/layers.go b/test/packetimpact/testbench/layers.go
deleted file mode 100644
index 817f5c261..000000000
--- a/test/packetimpact/testbench/layers.go
+++ /dev/null
@@ -1,1157 +0,0 @@
-// 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 (
- "encoding/hex"
- "fmt"
- "reflect"
- "strings"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "go.uber.org/multierr"
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/header"
-)
-
-// Layer is the interface that all encapsulations must implement.
-//
-// A Layer is an encapsulation in a packet, such as TCP, IPv4, IPv6, etc. A
-// 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
- // and a calculated checksum for TCP or IP. Some layers require information
- // from the previous or next layers in order to compute a default, such as
- // TCP's checksum or Ethernet's type, so each Layer has a doubly-linked list
- // to the layer's neighbors.
- ToBytes() ([]byte, error)
-
- // 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. The LayerBase is
- // ignored.
- match(Layer) bool
-
- // length in bytes of the current encapsulation
- length() int
-
- // next gets a pointer to the encapsulated Layer.
- next() Layer
-
- // prev gets a pointer to the Layer encapsulating this one.
- prev() Layer
-
- // setNext sets the pointer to the encapsulated Layer.
- setNext(Layer)
-
- // setPrev sets the pointer to the Layer encapsulating this one.
- setPrev(Layer)
-
- // merge overrides the values in the interface with the provided values.
- merge(Layer) error
-}
-
-// LayerBase is the common elements of all layers.
-type LayerBase struct {
- nextLayer Layer
- prevLayer Layer
-}
-
-func (lb *LayerBase) next() Layer {
- return lb.nextLayer
-}
-
-func (lb *LayerBase) prev() Layer {
- return lb.prevLayer
-}
-
-func (lb *LayerBase) setNext(l Layer) {
- lb.nextLayer = l
-}
-
-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 {
- if x == nil || y == nil {
- return true
- }
- // opt ignores comparison pairs where either of the inputs is a nil.
- opt := cmp.FilterValues(func(x, y interface{}) bool {
- 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.IgnoreTypes(LayerBase{}))
-}
-
-// mergeLayer merges y into x. Any fields for which y has a non-nil value, that
-// value overwrite the corresponding fields in x.
-func mergeLayer(x, y Layer) error {
- if y == nil {
- return nil
- }
- if reflect.TypeOf(x) != reflect.TypeOf(y) {
- return fmt.Errorf("can't merge %T into %T", y, x)
- }
- vx := reflect.ValueOf(x).Elem()
- vy := reflect.ValueOf(y).Elem()
- t := vy.Type()
- for i := 0; i < vy.NumField(); i++ {
- t := t.Field(i)
- if t.Anonymous {
- // Ignore the LayerBase in the Layer struct.
- continue
- }
- v := vy.Field(i)
- if v.IsNil() {
- continue
- }
- vx.Field(i).Set(v)
- }
- return nil
-}
-
-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
- }
- v = reflect.Indirect(v)
- if v.Kind() == reflect.Slice && v.Type().Elem().Kind() == reflect.Uint8 {
- ret = append(ret, fmt.Sprintf("%s:\n%v", t.Name, hex.Dump(v.Bytes())))
- } else {
- ret = append(ret, fmt.Sprintf("%s:%v", t.Name, v))
- }
- }
- return fmt.Sprintf("&%s{%s}", t, strings.Join(ret, " "))
-}
-
-// Ether can construct and match an ethernet encapsulation.
-type Ether struct {
- LayerBase
- SrcAddr *tcpip.LinkAddress
- DstAddr *tcpip.LinkAddress
- Type *tcpip.NetworkProtocolNumber
-}
-
-func (l *Ether) String() string {
- return stringLayer(l)
-}
-
-// ToBytes implements Layer.ToBytes.
-func (l *Ether) ToBytes() ([]byte, error) {
- b := make([]byte, header.EthernetMinimumSize)
- h := header.Ethernet(b)
- fields := &header.EthernetFields{}
- if l.SrcAddr != nil {
- fields.SrcAddr = *l.SrcAddr
- }
- if l.DstAddr != nil {
- fields.DstAddr = *l.DstAddr
- }
- if l.Type != nil {
- fields.Type = *l.Type
- } else {
- switch n := l.next().(type) {
- case *IPv4:
- fields.Type = header.IPv4ProtocolNumber
- case *IPv6:
- fields.Type = header.IPv6ProtocolNumber
- default:
- return nil, fmt.Errorf("ethernet header's next layer is unrecognized: %#v", n)
- }
- }
- h.Encode(fields)
- return h, nil
-}
-
-// LinkAddress is a helper routine that allocates a new tcpip.LinkAddress value
-// to store v and returns a pointer to it.
-func LinkAddress(v tcpip.LinkAddress) *tcpip.LinkAddress {
- return &v
-}
-
-// NetworkProtocolNumber is a helper routine that allocates a new
-// tcpip.NetworkProtocolNumber value to store v and returns a pointer to it.
-func NetworkProtocolNumber(v tcpip.NetworkProtocolNumber) *tcpip.NetworkProtocolNumber {
- 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) (Layer, layerParser) {
- h := header.Ethernet(b)
- ether := Ether{
- SrcAddr: LinkAddress(h.SourceAddress()),
- DstAddr: LinkAddress(h.DestinationAddress()),
- Type: NetworkProtocolNumber(h.Type()),
- }
- var nextParser layerParser
- switch h.Type() {
- case header.IPv4ProtocolNumber:
- nextParser = parseIPv4
- case header.IPv6ProtocolNumber:
- nextParser = parseIPv6
- default:
- // Assume that the rest is a payload.
- nextParser = parsePayload
- }
- return &ether, nextParser
-}
-
-func (l *Ether) match(other Layer) bool {
- return equalLayer(l, other)
-}
-
-func (l *Ether) length() int {
- return header.EthernetMinimumSize
-}
-
-// merge implements Layer.merge.
-func (l *Ether) merge(other Layer) error {
- return mergeLayer(l, other)
-}
-
-// IPv4 can construct and match an IPv4 encapsulation.
-type IPv4 struct {
- LayerBase
- IHL *uint8
- TOS *uint8
- TotalLength *uint16
- ID *uint16
- Flags *uint8
- FragmentOffset *uint16
- TTL *uint8
- Protocol *uint8
- Checksum *uint16
- SrcAddr *tcpip.Address
- DstAddr *tcpip.Address
-}
-
-func (l *IPv4) String() string {
- return stringLayer(l)
-}
-
-// ToBytes implements Layer.ToBytes.
-func (l *IPv4) ToBytes() ([]byte, error) {
- b := make([]byte, header.IPv4MinimumSize)
- h := header.IPv4(b)
- fields := &header.IPv4Fields{
- IHL: 20,
- TOS: 0,
- TotalLength: 0,
- ID: 0,
- Flags: 0,
- FragmentOffset: 0,
- TTL: 64,
- Protocol: 0,
- Checksum: 0,
- SrcAddr: tcpip.Address(""),
- DstAddr: tcpip.Address(""),
- }
- if l.TOS != nil {
- fields.TOS = *l.TOS
- }
- if l.TotalLength != nil {
- fields.TotalLength = *l.TotalLength
- } else {
- fields.TotalLength = uint16(l.length())
- current := l.next()
- for current != nil {
- fields.TotalLength += uint16(current.length())
- current = current.next()
- }
- }
- if l.ID != nil {
- fields.ID = *l.ID
- }
- if l.Flags != nil {
- fields.Flags = *l.Flags
- }
- if l.FragmentOffset != nil {
- fields.FragmentOffset = *l.FragmentOffset
- }
- if l.TTL != nil {
- fields.TTL = *l.TTL
- }
- if l.Protocol != nil {
- fields.Protocol = *l.Protocol
- } else {
- 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 as needed.
- return nil, fmt.Errorf("ipv4 header's next layer is unrecognized: %#v", n)
- }
- }
- if l.SrcAddr != nil {
- fields.SrcAddr = *l.SrcAddr
- }
- if l.DstAddr != nil {
- fields.DstAddr = *l.DstAddr
- }
- if l.Checksum != nil {
- fields.Checksum = *l.Checksum
- }
- h.Encode(fields)
- if l.Checksum == nil {
- h.SetChecksum(^h.CalculateChecksum())
- }
- return h, nil
-}
-
-// Uint16 is a helper routine that allocates a new
-// uint16 value to store v and returns a pointer to it.
-func Uint16(v uint16) *uint16 {
- return &v
-}
-
-// Uint8 is a helper routine that allocates a new
-// uint8 value to store v and returns a pointer to it.
-func Uint8(v uint8) *uint8 {
- return &v
-}
-
-// Address is a helper routine that allocates a new tcpip.Address value to store
-// v and returns a pointer to it.
-func Address(v tcpip.Address) *tcpip.Address {
- return &v
-}
-
-// parseIPv4 parses the bytes assuming that they start with an ipv4 header and
-// continues parsing further encapsulations.
-func parseIPv4(b []byte) (Layer, layerParser) {
- h := header.IPv4(b)
- tos, _ := h.TOS()
- ipv4 := IPv4{
- IHL: Uint8(h.HeaderLength()),
- TOS: &tos,
- TotalLength: Uint16(h.TotalLength()),
- ID: Uint16(h.ID()),
- Flags: Uint8(h.Flags()),
- FragmentOffset: Uint16(h.FragmentOffset()),
- TTL: Uint8(h.TTL()),
- Protocol: Uint8(h.Protocol()),
- Checksum: Uint16(h.Checksum()),
- SrcAddr: Address(h.SourceAddress()),
- DstAddr: Address(h.DestinationAddress()),
- }
- 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 &ipv4, nextParser
-}
-
-func (l *IPv4) match(other Layer) bool {
- return equalLayer(l, other)
-}
-
-func (l *IPv4) length() int {
- if l.IHL == nil {
- return header.IPv4MinimumSize
- }
- return int(*l.IHL)
-}
-
-// merge implements Layer.merge.
-func (l *IPv4) merge(other Layer) error {
- return mergeLayer(l, other)
-}
-
-// IPv6 can construct and match an IPv6 encapsulation.
-type IPv6 struct {
- LayerBase
- TrafficClass *uint8
- FlowLabel *uint32
- PayloadLength *uint16
- NextHeader *uint8
- HopLimit *uint8
- SrcAddr *tcpip.Address
- DstAddr *tcpip.Address
-}
-
-func (l *IPv6) String() string {
- return stringLayer(l)
-}
-
-// ToBytes implements Layer.ToBytes.
-func (l *IPv6) ToBytes() ([]byte, error) {
- b := make([]byte, header.IPv6MinimumSize)
- h := header.IPv6(b)
- fields := &header.IPv6Fields{
- HopLimit: 64,
- }
- if l.TrafficClass != nil {
- fields.TrafficClass = *l.TrafficClass
- }
- if l.FlowLabel != nil {
- fields.FlowLabel = *l.FlowLabel
- }
- if l.PayloadLength != nil {
- fields.PayloadLength = *l.PayloadLength
- } else {
- for current := l.next(); current != nil; current = current.next() {
- fields.PayloadLength += uint16(current.length())
- }
- }
- if l.NextHeader != nil {
- fields.NextHeader = *l.NextHeader
- } else {
- switch n := l.next().(type) {
- case *TCP:
- fields.NextHeader = uint8(header.TCPProtocolNumber)
- case *UDP:
- fields.NextHeader = uint8(header.UDPProtocolNumber)
- case *ICMPv6:
- fields.NextHeader = uint8(header.ICMPv6ProtocolNumber)
- default:
- // TODO(b/150301488): Support more protocols as needed.
- return nil, fmt.Errorf("ToBytes can't deduce the IPv6 header's next protocol: %#v", n)
- }
- }
- if l.HopLimit != nil {
- fields.HopLimit = *l.HopLimit
- }
- if l.SrcAddr != nil {
- fields.SrcAddr = *l.SrcAddr
- }
- if l.DstAddr != nil {
- fields.DstAddr = *l.DstAddr
- }
- h.Encode(fields)
- return h, nil
-}
-
-// parseIPv6 parses the bytes assuming that they start with an ipv6 header and
-// continues parsing further encapsulations.
-func parseIPv6(b []byte) (Layer, layerParser) {
- h := header.IPv6(b)
- tos, flowLabel := h.TOS()
- ipv6 := IPv6{
- TrafficClass: &tos,
- FlowLabel: &flowLabel,
- PayloadLength: Uint16(h.PayloadLength()),
- NextHeader: Uint8(h.NextHeader()),
- HopLimit: Uint8(h.HopLimit()),
- SrcAddr: Address(h.SourceAddress()),
- DstAddr: Address(h.DestinationAddress()),
- }
- var nextParser layerParser
- switch h.TransportProtocol() {
- case header.TCPProtocolNumber:
- nextParser = parseTCP
- case header.UDPProtocolNumber:
- nextParser = parseUDP
- case header.ICMPv6ProtocolNumber:
- nextParser = parseICMPv6
- default:
- // Assume that the rest is a payload.
- nextParser = parsePayload
- }
- return &ipv6, nextParser
-}
-
-func (l *IPv6) match(other Layer) bool {
- return equalLayer(l, other)
-}
-
-func (l *IPv6) length() int {
- return header.IPv6MinimumSize
-}
-
-// merge overrides the values in l with the values from other but only in fields
-// where the value is not nil.
-func (l *IPv6) merge(other Layer) error {
- return mergeLayer(l, other)
-}
-
-// ICMPv6 can construct and match an ICMPv6 encapsulation.
-type ICMPv6 struct {
- LayerBase
- Type *header.ICMPv6Type
- Code *byte
- Checksum *uint16
- NDPPayload []byte
-}
-
-func (l *ICMPv6) String() string {
- // TODO(eyalsoha): Do something smarter here when *l.Type is ParameterProblem?
- // We could parse the contents of the Payload as if it were an IPv6 packet.
- return stringLayer(l)
-}
-
-// ToBytes implements Layer.ToBytes.
-func (l *ICMPv6) ToBytes() ([]byte, error) {
- b := make([]byte, header.ICMPv6HeaderSize+len(l.NDPPayload))
- h := header.ICMPv6(b)
- if l.Type != nil {
- h.SetType(*l.Type)
- }
- if l.Code != nil {
- h.SetCode(*l.Code)
- }
- copy(h.NDPPayload(), l.NDPPayload)
- if l.Checksum != nil {
- h.SetChecksum(*l.Checksum)
- } else {
- ipv6 := l.prev().(*IPv6)
- h.SetChecksum(header.ICMPv6Checksum(h, *ipv6.SrcAddr, *ipv6.DstAddr, buffer.VectorisedView{}))
- }
- return h, nil
-}
-
-// ICMPv6Type is a helper routine that allocates a new ICMPv6Type value to store
-// v and returns a pointer to it.
-func ICMPv6Type(v header.ICMPv6Type) *header.ICMPv6Type {
- return &v
-}
-
-// Byte is a helper routine that allocates a new byte value to store
-// v and returns a pointer to it.
-func Byte(v byte) *byte {
- return &v
-}
-
-// parseICMPv6 parses the bytes assuming that they start with an ICMPv6 header.
-func parseICMPv6(b []byte) (Layer, layerParser) {
- h := header.ICMPv6(b)
- icmpv6 := ICMPv6{
- Type: ICMPv6Type(h.Type()),
- Code: Byte(h.Code()),
- Checksum: Uint16(h.Checksum()),
- NDPPayload: h.NDPPayload(),
- }
- return &icmpv6, nil
-}
-
-func (l *ICMPv6) match(other Layer) bool {
- return equalLayer(l, other)
-}
-
-func (l *ICMPv6) length() int {
- return header.ICMPv6HeaderSize + len(l.NDPPayload)
-}
-
-// merge overrides the values in l with the values from other but only in fields
-// where the value is not nil.
-func (l *ICMPv6) merge(other Layer) error {
- return mergeLayer(l, other)
-}
-
-// TCP can construct and match a TCP encapsulation.
-type TCP struct {
- LayerBase
- SrcPort *uint16
- DstPort *uint16
- SeqNum *uint32
- AckNum *uint32
- DataOffset *uint8
- Flags *uint8
- WindowSize *uint16
- Checksum *uint16
- UrgentPointer *uint16
-}
-
-func (l *TCP) String() string {
- return stringLayer(l)
-}
-
-// ToBytes implements Layer.ToBytes.
-func (l *TCP) ToBytes() ([]byte, error) {
- b := make([]byte, header.TCPMinimumSize)
- h := header.TCP(b)
- if l.SrcPort != nil {
- h.SetSourcePort(*l.SrcPort)
- }
- if l.DstPort != nil {
- h.SetDestinationPort(*l.DstPort)
- }
- if l.SeqNum != nil {
- h.SetSequenceNumber(*l.SeqNum)
- }
- if l.AckNum != nil {
- h.SetAckNumber(*l.AckNum)
- }
- 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)
- }
- if l.Checksum != nil {
- h.SetChecksum(*l.Checksum)
- return h, nil
- }
- if err := setTCPChecksum(&h, l); err != nil {
- return nil, err
- }
- return h, nil
-}
-
-// 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 := l.prev().(type) {
- case *IPv4:
- xsum = header.PseudoHeaderChecksum(protoNumber, *s.SrcAddr, *s.DstAddr, totalLength)
- default:
- // TODO(b/150301488): Support more protocols, like IPv6.
- return 0, fmt.Errorf("can't get src and dst addr from previous layer: %#v", s)
- }
- var payloadBytes buffer.VectorisedView
- for current := l.next(); current != nil; current = current.next() {
- payload, err := current.ToBytes()
- if err != nil {
- return 0, fmt.Errorf("can't get bytes for next header: %s", payload)
- }
- 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
-}
-
-// Uint32 is a helper routine that allocates a new
-// uint32 value to store v and returns a pointer to it.
-func Uint32(v uint32) *uint32 {
- return &v
-}
-
-// parseTCP parses the bytes assuming that they start with a tcp header and
-// continues parsing further encapsulations.
-func parseTCP(b []byte) (Layer, layerParser) {
- h := header.TCP(b)
- tcp := TCP{
- SrcPort: Uint16(h.SourcePort()),
- DstPort: Uint16(h.DestinationPort()),
- SeqNum: Uint32(h.SequenceNumber()),
- AckNum: Uint32(h.AckNumber()),
- DataOffset: Uint8(h.DataOffset()),
- Flags: Uint8(h.Flags()),
- WindowSize: Uint16(h.WindowSize()),
- Checksum: Uint16(h.Checksum()),
- UrgentPointer: Uint16(h.UrgentPointer()),
- }
- return &tcp, parsePayload
-}
-
-func (l *TCP) match(other Layer) bool {
- return equalLayer(l, other)
-}
-
-func (l *TCP) length() int {
- if l.DataOffset == nil {
- return header.TCPMinimumSize
- }
- return int(*l.DataOffset)
-}
-
-// merge implements Layer.merge.
-func (l *TCP) merge(other Layer) error {
- return mergeLayer(l, other)
-}
-
-// 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)
-}
-
-// ToBytes implements Layer.ToBytes.
-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 implements Layer.merge.
-func (l *UDP) merge(other Layer) error {
- return mergeLayer(l, other)
-}
-
-// 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) (Layer, layerParser) {
- payload := Payload{
- Bytes: b,
- }
- return &payload, nil
-}
-
-// ToBytes implements Layer.ToBytes.
-func (l *Payload) ToBytes() ([]byte, error) {
- return l.Bytes, nil
-}
-
-func (l *Payload) match(other Layer) bool {
- return equalLayer(l, other)
-}
-
-func (l *Payload) length() int {
- return len(l.Bytes)
-}
-
-// merge implements Layer.merge.
-func (l *Payload) merge(other Layer) error {
- return mergeLayer(l, other)
-}
-
-// Layers is an array of Layer and supports similar functions to Layer.
-type Layers []Layer
-
-// 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)
- }
- }
-}
-
-// ToBytes converts the Layers into bytes. It creates a linked list of the Layer
-// structs and then concatentates the output of ToBytes on each Layer.
-func (ls *Layers) ToBytes() ([]byte, error) {
- ls.linkLayers()
- outBytes := []byte{}
- for _, l := range *ls {
- layerBytes, err := l.ToBytes()
- if err != nil {
- return nil, err
- }
- outBytes = append(outBytes, layerBytes...)
- }
- return outBytes, nil
-}
-
-func (ls *Layers) match(other Layers) bool {
- if len(*ls) > len(other) {
- return false
- }
- for i, l := range *ls {
- if !equalLayer(l, other[i]) {
- return false
- }
- }
- return true
-}
-
-// layerDiff stores the diffs for each field along with the label for the Layer.
-// If rows is nil, that means that there was no diff.
-type layerDiff struct {
- label string
- rows []layerDiffRow
-}
-
-// layerDiffRow stores the fields and corresponding values for two got and want
-// layers. If the value was nil then the string stored is the empty string.
-type layerDiffRow struct {
- field, got, want string
-}
-
-// diffLayer extracts all differing fields between two layers.
-func diffLayer(got, want Layer) []layerDiffRow {
- vGot := reflect.ValueOf(got).Elem()
- vWant := reflect.ValueOf(want).Elem()
- if vGot.Type() != vWant.Type() {
- return nil
- }
- t := vGot.Type()
- var result []layerDiffRow
- for i := 0; i < t.NumField(); i++ {
- t := t.Field(i)
- if t.Anonymous {
- // Ignore the LayerBase in the Layer struct.
- continue
- }
- vGot := vGot.Field(i)
- vWant := vWant.Field(i)
- gotString := ""
- if !vGot.IsNil() {
- gotString = fmt.Sprint(reflect.Indirect(vGot))
- }
- wantString := ""
- if !vWant.IsNil() {
- wantString = fmt.Sprint(reflect.Indirect(vWant))
- }
- result = append(result, layerDiffRow{t.Name, gotString, wantString})
- }
- return result
-}
-
-// layerType returns a concise string describing the type of the Layer, like
-// "TCP", or "IPv6".
-func layerType(l Layer) string {
- return reflect.TypeOf(l).Elem().Name()
-}
-
-// diff compares Layers and returns a representation of the difference. Each
-// Layer in the Layers is pairwise compared. If an element in either is nil, it
-// is considered a match with the other Layer. If two Layers have differing
-// types, they don't match regardless of the contents. If two Layers have the
-// same type then the fields in the Layer are pairwise compared. Fields that are
-// nil always match. Two non-nil fields only match if they point to equal
-// values. diff returns an empty string if and only if *ls and other match.
-func (ls *Layers) diff(other Layers) string {
- var allDiffs []layerDiff
- // Check the cases where one list is longer than the other, where one or both
- // elements are nil, where the sides have different types, and where the sides
- // have the same type.
- for i := 0; i < len(*ls) || i < len(other); i++ {
- if i >= len(*ls) {
- // Matching ls against other where other is longer than ls. missing
- // matches everything so we just include a label without any rows. Having
- // no rows is a sign that there was no diff.
- allDiffs = append(allDiffs, layerDiff{
- label: "missing matches " + layerType(other[i]),
- })
- continue
- }
-
- if i >= len(other) {
- // Matching ls against other where ls is longer than other. missing
- // matches everything so we just include a label without any rows. Having
- // no rows is a sign that there was no diff.
- allDiffs = append(allDiffs, layerDiff{
- label: layerType((*ls)[i]) + " matches missing",
- })
- continue
- }
-
- if (*ls)[i] == nil && other[i] == nil {
- // Matching ls against other where both elements are nil. nil matches
- // everything so we just include a label without any rows. Having no rows
- // is a sign that there was no diff.
- allDiffs = append(allDiffs, layerDiff{
- label: "nil matches nil",
- })
- continue
- }
-
- if (*ls)[i] == nil {
- // Matching ls against other where the element in ls is nil. nil matches
- // everything so we just include a label without any rows. Having no rows
- // is a sign that there was no diff.
- allDiffs = append(allDiffs, layerDiff{
- label: "nil matches " + layerType(other[i]),
- })
- continue
- }
-
- if other[i] == nil {
- // Matching ls against other where the element in other is nil. nil
- // matches everything so we just include a label without any rows. Having
- // no rows is a sign that there was no diff.
- allDiffs = append(allDiffs, layerDiff{
- label: layerType((*ls)[i]) + " matches nil",
- })
- continue
- }
-
- if reflect.TypeOf((*ls)[i]) == reflect.TypeOf(other[i]) {
- // Matching ls against other where both elements have the same type. Match
- // each field pairwise and only report a diff if there is a mismatch,
- // which is only when both sides are non-nil and have differring values.
- diff := diffLayer((*ls)[i], other[i])
- var layerDiffRows []layerDiffRow
- for _, d := range diff {
- if d.got == "" || d.want == "" || d.got == d.want {
- continue
- }
- layerDiffRows = append(layerDiffRows, layerDiffRow{
- d.field,
- d.got,
- d.want,
- })
- }
- if len(layerDiffRows) > 0 {
- allDiffs = append(allDiffs, layerDiff{
- label: layerType((*ls)[i]),
- rows: layerDiffRows,
- })
- } else {
- allDiffs = append(allDiffs, layerDiff{
- label: layerType((*ls)[i]) + " matches " + layerType(other[i]),
- // Having no rows is a sign that there was no diff.
- })
- }
- continue
- }
- // Neither side is nil and the types are different, so we'll display one
- // side then the other.
- allDiffs = append(allDiffs, layerDiff{
- label: layerType((*ls)[i]) + " doesn't match " + layerType(other[i]),
- })
- diff := diffLayer((*ls)[i], (*ls)[i])
- layerDiffRows := []layerDiffRow{}
- for _, d := range diff {
- if len(d.got) == 0 {
- continue
- }
- layerDiffRows = append(layerDiffRows, layerDiffRow{
- d.field,
- d.got,
- "",
- })
- }
- allDiffs = append(allDiffs, layerDiff{
- label: layerType((*ls)[i]),
- rows: layerDiffRows,
- })
-
- layerDiffRows = []layerDiffRow{}
- diff = diffLayer(other[i], other[i])
- for _, d := range diff {
- if len(d.want) == 0 {
- continue
- }
- layerDiffRows = append(layerDiffRows, layerDiffRow{
- d.field,
- "",
- d.want,
- })
- }
- allDiffs = append(allDiffs, layerDiff{
- label: layerType(other[i]),
- rows: layerDiffRows,
- })
- }
-
- output := ""
- // These are for output formatting.
- maxLabelLen, maxFieldLen, maxGotLen, maxWantLen := 0, 0, 0, 0
- foundOne := false
- for _, l := range allDiffs {
- if len(l.label) > maxLabelLen && len(l.rows) > 0 {
- maxLabelLen = len(l.label)
- }
- if l.rows != nil {
- foundOne = true
- }
- for _, r := range l.rows {
- if len(r.field) > maxFieldLen {
- maxFieldLen = len(r.field)
- }
- if l := len(fmt.Sprint(r.got)); l > maxGotLen {
- maxGotLen = l
- }
- if l := len(fmt.Sprint(r.want)); l > maxWantLen {
- maxWantLen = l
- }
- }
- }
- if !foundOne {
- return ""
- }
- for _, l := range allDiffs {
- if len(l.rows) == 0 {
- output += "(" + l.label + ")\n"
- continue
- }
- for i, r := range l.rows {
- var label string
- if i == 0 {
- label = l.label + ":"
- }
- output += fmt.Sprintf(
- "%*s %*s %*v %*v\n",
- maxLabelLen+1, label,
- maxFieldLen+1, r.field+":",
- maxGotLen, r.got,
- maxWantLen, r.want,
- )
- }
- }
- return output
-}
-
-// merge merges the other Layers into ls. If the other Layers is longer, those
-// additional Layer structs are added to ls. The errors from merging are
-// collected and returned.
-func (ls *Layers) merge(other Layers) error {
- var errs error
- for i, o := range other {
- if i < len(*ls) {
- errs = multierr.Combine(errs, (*ls)[i].merge(o))
- } else {
- *ls = append(*ls, o)
- }
- }
- return errs
-}
diff --git a/test/packetimpact/testbench/layers_test.go b/test/packetimpact/testbench/layers_test.go
deleted file mode 100644
index 96f72de5b..000000000
--- a/test/packetimpact/testbench/layers_test.go
+++ /dev/null
@@ -1,395 +0,0 @@
-// 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"
-
- "github.com/mohae/deepcopy"
- "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 TestLayerMergeMismatch(t *testing.T) {
- tcp := &TCP{}
- otherTCP := &TCP{}
- ipv4 := &IPv4{}
- ether := &Ether{}
- for _, tt := range []struct {
- a, b Layer
- success bool
- }{
- {tcp, tcp, true},
- {tcp, otherTCP, true},
- {tcp, ipv4, false},
- {tcp, ether, false},
- {tcp, nil, true},
-
- {otherTCP, otherTCP, true},
- {otherTCP, ipv4, false},
- {otherTCP, ether, false},
- {otherTCP, nil, true},
-
- {ipv4, ipv4, true},
- {ipv4, ether, false},
- {ipv4, nil, true},
-
- {ether, ether, true},
- {ether, nil, true},
- } {
- if err := tt.a.merge(tt.b); (err == nil) != tt.success {
- t.Errorf("%s.merge(%s) got %s, wanted the opposite", tt.a, tt.b, err)
- }
- if tt.b != nil {
- if err := tt.b.merge(tt.a); (err == nil) != tt.success {
- t.Errorf("%s.merge(%s) got %s, wanted the opposite", tt.b, tt.a, err)
- }
- }
- }
-}
-
-func TestLayerMerge(t *testing.T) {
- zero := Uint32(0)
- one := Uint32(1)
- two := Uint32(2)
- empty := []byte{}
- foo := []byte("foo")
- bar := []byte("bar")
- for _, tt := range []struct {
- a, b Layer
- want Layer
- }{
- {&TCP{AckNum: nil}, &TCP{AckNum: nil}, &TCP{AckNum: nil}},
- {&TCP{AckNum: nil}, &TCP{AckNum: zero}, &TCP{AckNum: zero}},
- {&TCP{AckNum: nil}, &TCP{AckNum: one}, &TCP{AckNum: one}},
- {&TCP{AckNum: nil}, &TCP{AckNum: two}, &TCP{AckNum: two}},
- {&TCP{AckNum: nil}, nil, &TCP{AckNum: nil}},
-
- {&TCP{AckNum: zero}, &TCP{AckNum: nil}, &TCP{AckNum: zero}},
- {&TCP{AckNum: zero}, &TCP{AckNum: zero}, &TCP{AckNum: zero}},
- {&TCP{AckNum: zero}, &TCP{AckNum: one}, &TCP{AckNum: one}},
- {&TCP{AckNum: zero}, &TCP{AckNum: two}, &TCP{AckNum: two}},
- {&TCP{AckNum: zero}, nil, &TCP{AckNum: zero}},
-
- {&TCP{AckNum: one}, &TCP{AckNum: nil}, &TCP{AckNum: one}},
- {&TCP{AckNum: one}, &TCP{AckNum: zero}, &TCP{AckNum: zero}},
- {&TCP{AckNum: one}, &TCP{AckNum: one}, &TCP{AckNum: one}},
- {&TCP{AckNum: one}, &TCP{AckNum: two}, &TCP{AckNum: two}},
- {&TCP{AckNum: one}, nil, &TCP{AckNum: one}},
-
- {&TCP{AckNum: two}, &TCP{AckNum: nil}, &TCP{AckNum: two}},
- {&TCP{AckNum: two}, &TCP{AckNum: zero}, &TCP{AckNum: zero}},
- {&TCP{AckNum: two}, &TCP{AckNum: one}, &TCP{AckNum: one}},
- {&TCP{AckNum: two}, &TCP{AckNum: two}, &TCP{AckNum: two}},
- {&TCP{AckNum: two}, nil, &TCP{AckNum: two}},
-
- {&Payload{Bytes: nil}, &Payload{Bytes: nil}, &Payload{Bytes: nil}},
- {&Payload{Bytes: nil}, &Payload{Bytes: empty}, &Payload{Bytes: empty}},
- {&Payload{Bytes: nil}, &Payload{Bytes: foo}, &Payload{Bytes: foo}},
- {&Payload{Bytes: nil}, &Payload{Bytes: bar}, &Payload{Bytes: bar}},
- {&Payload{Bytes: nil}, nil, &Payload{Bytes: nil}},
-
- {&Payload{Bytes: empty}, &Payload{Bytes: nil}, &Payload{Bytes: empty}},
- {&Payload{Bytes: empty}, &Payload{Bytes: empty}, &Payload{Bytes: empty}},
- {&Payload{Bytes: empty}, &Payload{Bytes: foo}, &Payload{Bytes: foo}},
- {&Payload{Bytes: empty}, &Payload{Bytes: bar}, &Payload{Bytes: bar}},
- {&Payload{Bytes: empty}, nil, &Payload{Bytes: empty}},
-
- {&Payload{Bytes: foo}, &Payload{Bytes: nil}, &Payload{Bytes: foo}},
- {&Payload{Bytes: foo}, &Payload{Bytes: empty}, &Payload{Bytes: empty}},
- {&Payload{Bytes: foo}, &Payload{Bytes: foo}, &Payload{Bytes: foo}},
- {&Payload{Bytes: foo}, &Payload{Bytes: bar}, &Payload{Bytes: bar}},
- {&Payload{Bytes: foo}, nil, &Payload{Bytes: foo}},
-
- {&Payload{Bytes: bar}, &Payload{Bytes: nil}, &Payload{Bytes: bar}},
- {&Payload{Bytes: bar}, &Payload{Bytes: empty}, &Payload{Bytes: empty}},
- {&Payload{Bytes: bar}, &Payload{Bytes: foo}, &Payload{Bytes: foo}},
- {&Payload{Bytes: bar}, &Payload{Bytes: bar}, &Payload{Bytes: bar}},
- {&Payload{Bytes: bar}, nil, &Payload{Bytes: bar}},
- } {
- a := deepcopy.Copy(tt.a).(Layer)
- if err := a.merge(tt.b); err != nil {
- t.Errorf("%s.merge(%s) = %s, wanted nil", tt.a, tt.b, err)
- continue
- }
- if a.String() != tt.want.String() {
- t.Errorf("%s.merge(%s) merge result got %s, want %s", tt.a, tt.b, a, 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" +
- "}",
- },
- {
- name: "Payload",
- l: &Payload{
- Bytes: []byte("Hooray for packetimpact."),
- },
- want: "&testbench.Payload{Bytes:\n" +
- "00000000 48 6f 6f 72 61 79 20 66 6f 72 20 70 61 63 6b 65 |Hooray for packe|\n" +
- "00000010 74 69 6d 70 61 63 74 2e |timpact.|\n" +
- "}",
- },
- } {
- 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)
- }
- })
- }
-}
-
-func TestConnectionMatch(t *testing.T) {
- conn := Connection{
- layerStates: []layerState{&etherState{}},
- }
- protoNum0 := tcpip.NetworkProtocolNumber(0)
- protoNum1 := tcpip.NetworkProtocolNumber(1)
- for _, tt := range []struct {
- description string
- override, received Layers
- wantMatch bool
- }{
- {
- description: "shorter override",
- override: []Layer{&Ether{}},
- received: []Layer{&Ether{}, &Payload{Bytes: []byte("hello")}},
- wantMatch: true,
- },
- {
- description: "longer override",
- override: []Layer{&Ether{}, &Payload{Bytes: []byte("hello")}},
- received: []Layer{&Ether{}},
- wantMatch: false,
- },
- {
- description: "ether layer mismatch",
- override: []Layer{&Ether{Type: &protoNum0}},
- received: []Layer{&Ether{Type: &protoNum1}},
- wantMatch: false,
- },
- {
- description: "both nil",
- override: nil,
- received: nil,
- wantMatch: false,
- },
- {
- description: "nil override",
- override: nil,
- received: []Layer{&Ether{}},
- wantMatch: true,
- },
- } {
- t.Run(tt.description, func(t *testing.T) {
- if gotMatch := conn.match(tt.override, tt.received); gotMatch != tt.wantMatch {
- t.Fatalf("conn.match(%s, %s) = %t, want %t", tt.override, tt.received, gotMatch, tt.wantMatch)
- }
- })
- }
-}
-
-func TestLayersDiff(t *testing.T) {
- for _, tt := range []struct {
- x, y Layers
- want string
- }{
- {
- Layers{&Ether{Type: NetworkProtocolNumber(12)}, &TCP{DataOffset: Uint8(5), SeqNum: Uint32(5)}},
- Layers{&Ether{Type: NetworkProtocolNumber(13)}, &TCP{DataOffset: Uint8(7), SeqNum: Uint32(6)}},
- "Ether: Type: 12 13\n" +
- " TCP: SeqNum: 5 6\n" +
- " DataOffset: 5 7\n",
- },
- {
- Layers{&Ether{Type: NetworkProtocolNumber(12)}, &UDP{SrcPort: Uint16(123)}},
- Layers{&Ether{Type: NetworkProtocolNumber(13)}, &TCP{DataOffset: Uint8(7), SeqNum: Uint32(6)}},
- "Ether: Type: 12 13\n" +
- "(UDP doesn't match TCP)\n" +
- " UDP: SrcPort: 123 \n" +
- " TCP: SeqNum: 6\n" +
- " DataOffset: 7\n",
- },
- {
- Layers{&UDP{SrcPort: Uint16(123)}},
- Layers{&Ether{Type: NetworkProtocolNumber(13)}, &TCP{DataOffset: Uint8(7), SeqNum: Uint32(6)}},
- "(UDP doesn't match Ether)\n" +
- " UDP: SrcPort: 123 \n" +
- "Ether: Type: 13\n" +
- "(missing matches TCP)\n",
- },
- {
- Layers{nil, &UDP{SrcPort: Uint16(123)}},
- Layers{&Ether{Type: NetworkProtocolNumber(13)}, &TCP{DataOffset: Uint8(7), SeqNum: Uint32(6)}},
- "(nil matches Ether)\n" +
- "(UDP doesn't match TCP)\n" +
- "UDP: SrcPort: 123 \n" +
- "TCP: SeqNum: 6\n" +
- " DataOffset: 7\n",
- },
- {
- Layers{&Ether{Type: NetworkProtocolNumber(13)}, &IPv4{IHL: Uint8(4)}, &TCP{DataOffset: Uint8(7), SeqNum: Uint32(6)}},
- Layers{&Ether{Type: NetworkProtocolNumber(13)}, &IPv4{IHL: Uint8(6)}, &TCP{DataOffset: Uint8(7), SeqNum: Uint32(6)}},
- "(Ether matches Ether)\n" +
- "IPv4: IHL: 4 6\n" +
- "(TCP matches TCP)\n",
- },
- {
- Layers{&Payload{Bytes: []byte("foo")}},
- Layers{&Payload{Bytes: []byte("bar")}},
- "Payload: Bytes: [102 111 111] [98 97 114]\n",
- },
- {
- Layers{&Payload{Bytes: []byte("")}},
- Layers{&Payload{}},
- "",
- },
- {
- Layers{&Payload{Bytes: []byte("")}},
- Layers{&Payload{Bytes: []byte("")}},
- "",
- },
- {
- Layers{&UDP{}},
- Layers{&TCP{}},
- "(UDP doesn't match TCP)\n" +
- "(UDP)\n" +
- "(TCP)\n",
- },
- } {
- if got := tt.x.diff(tt.y); got != tt.want {
- t.Errorf("%s.diff(%s) = %q, want %q", tt.x, tt.y, got, tt.want)
- }
- if tt.x.match(tt.y) != (tt.x.diff(tt.y) == "") {
- t.Errorf("match and diff of %s and %s disagree", tt.x, tt.y)
- }
- if tt.y.match(tt.x) != (tt.y.diff(tt.x) == "") {
- t.Errorf("match and diff of %s and %s disagree", tt.y, tt.x)
- }
- }
-}
diff --git a/test/packetimpact/testbench/rawsockets.go b/test/packetimpact/testbench/rawsockets.go
deleted file mode 100644
index ff722d4a6..000000000
--- a/test/packetimpact/testbench/rawsockets.go
+++ /dev/null
@@ -1,183 +0,0 @@
-// 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 (
- "encoding/binary"
- "flag"
- "fmt"
- "math"
- "net"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/usermem"
-)
-
-var device = flag.String("device", "", "local device for test packets")
-
-// Sniffer can sniff raw packets on the wire.
-type Sniffer struct {
- t *testing.T
- fd int
-}
-
-func htons(x uint16) uint16 {
- buf := [2]byte{}
- binary.BigEndian.PutUint16(buf[:], x)
- return usermem.ByteOrder.Uint16(buf[:])
-}
-
-// NewSniffer creates a Sniffer connected to *device.
-func NewSniffer(t *testing.T) (Sniffer, error) {
- flag.Parse()
- snifferFd, err := unix.Socket(unix.AF_PACKET, unix.SOCK_RAW, int(htons(unix.ETH_P_ALL)))
- 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,
- }, nil
-}
-
-// maxReadSize should be large enough for the maximum frame size in bytes. If a
-// packet too large for the buffer arrives, the test will get a fatal error.
-const maxReadSize int = 65536
-
-// Recv tries to read one frame until the timeout is up.
-func (s *Sniffer) Recv(timeout time.Duration) []byte {
- deadline := time.Now().Add(timeout)
- for {
- timeout = deadline.Sub(time.Now())
- if timeout <= 0 {
- return nil
- }
- whole, frac := math.Modf(timeout.Seconds())
- tv := unix.Timeval{
- Sec: int64(whole),
- Usec: int64(frac * float64(time.Microsecond/time.Second)),
- }
-
- if err := unix.SetsockoptTimeval(s.fd, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &tv); err != nil {
- s.t.Fatalf("can't setsockopt SO_RCVTIMEO: %s", err)
- }
-
- buf := make([]byte, maxReadSize)
- nread, _, err := unix.Recvfrom(s.fd, buf, unix.MSG_TRUNC)
- if err == unix.EINTR || err == unix.EAGAIN {
- // There was a timeout.
- continue
- }
- if err != nil {
- s.t.Fatalf("can't read: %s", err)
- }
- if nread > maxReadSize {
- s.t.Fatalf("received a truncated frame of %d bytes", nread)
- }
- return buf[:nread]
- }
-}
-
-// Drain drains the Sniffer's socket receive buffer by receiving until there's
-// nothing else to receive.
-func (s *Sniffer) Drain() {
- s.t.Helper()
- flags, err := unix.FcntlInt(uintptr(s.fd), unix.F_GETFL, 0)
- if err != nil {
- s.t.Fatalf("failed to get sniffer socket fd flags: %s", err)
- }
- if _, err := unix.FcntlInt(uintptr(s.fd), unix.F_SETFL, flags|unix.O_NONBLOCK); err != nil {
- s.t.Fatalf("failed to make sniffer socket non-blocking: %s", err)
- }
- for {
- buf := make([]byte, maxReadSize)
- _, _, err := unix.Recvfrom(s.fd, buf, unix.MSG_TRUNC)
- if err == unix.EINTR || err == unix.EAGAIN || err == unix.EWOULDBLOCK {
- break
- }
- }
- if _, err := unix.FcntlInt(uintptr(s.fd), unix.F_SETFL, flags); err != nil {
- s.t.Fatalf("failed to restore sniffer socket fd flags: %s", err)
- }
-}
-
-// close the socket that Sniffer is using.
-func (s *Sniffer) close() error {
- if err := unix.Close(s.fd); err != nil {
- return fmt.Errorf("can't close sniffer socket: %w", err)
- }
- s.fd = -1
- return nil
-}
-
-// Injector can inject raw frames.
-type Injector struct {
- t *testing.T
- fd int
-}
-
-// NewInjector creates a new injector on *device.
-func NewInjector(t *testing.T) (Injector, error) {
- flag.Parse()
- ifInfo, err := net.InterfaceByName(*device)
- if err != nil {
- return Injector{}, err
- }
-
- var haddr [8]byte
- copy(haddr[:], ifInfo.HardwareAddr)
- sa := unix.SockaddrLinklayer{
- Protocol: unix.ETH_P_IP,
- Ifindex: ifInfo.Index,
- Halen: uint8(len(ifInfo.HardwareAddr)),
- Addr: haddr,
- }
-
- injectFd, err := unix.Socket(unix.AF_PACKET, unix.SOCK_RAW, int(htons(unix.ETH_P_ALL)))
- if err != nil {
- return Injector{}, err
- }
- if err := unix.Bind(injectFd, &sa); err != nil {
- return Injector{}, err
- }
- return Injector{
- t: t,
- fd: injectFd,
- }, nil
-}
-
-// Send a raw frame.
-func (i *Injector) Send(b []byte) {
- if _, err := unix.Write(i.fd, b); err != nil {
- i.t.Fatalf("can't write: %s", err)
- }
-}
-
-// close the underlying socket.
-func (i *Injector) close() error {
- if err := unix.Close(i.fd); err != nil {
- return fmt.Errorf("can't close sniffer socket: %w", err)
- }
- i.fd = -1
- return nil
-}
diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD
deleted file mode 100644
index 42f87e3f3..000000000
--- a/test/packetimpact/tests/BUILD
+++ /dev/null
@@ -1,115 +0,0 @@
-load("defs.bzl", "packetimpact_go_test")
-
-package(
- default_visibility = ["//test/packetimpact:__subpackages__"],
- licenses = ["notice"],
-)
-
-packetimpact_go_test(
- name = "fin_wait2_timeout",
- srcs = ["fin_wait2_timeout_test.go"],
- deps = [
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_go_test(
- name = "udp_recv_multicast",
- srcs = ["udp_recv_multicast_test.go"],
- # TODO(b/152813495): Fix netstack then remove the line below.
- netstack = False,
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_go_test(
- name = "tcp_window_shrink",
- srcs = ["tcp_window_shrink_test.go"],
- # TODO(b/153202472): Fix netstack then remove the line below.
- netstack = False,
- deps = [
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_go_test(
- name = "tcp_outside_the_window",
- srcs = ["tcp_outside_the_window_test.go"],
- deps = [
- "//pkg/tcpip/header",
- "//pkg/tcpip/seqnum",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_go_test(
- name = "tcp_noaccept_close_rst",
- srcs = ["tcp_noaccept_close_rst_test.go"],
- deps = [
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_go_test(
- name = "tcp_should_piggyback",
- srcs = ["tcp_should_piggyback_test.go"],
- # TODO(b/153680566): Fix netstack then remove the line below.
- netstack = False,
- deps = [
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_go_test(
- name = "tcp_close_wait_ack",
- srcs = ["tcp_close_wait_ack_test.go"],
- # TODO(b/153574037): Fix netstack then remove the line below.
- netstack = False,
- deps = [
- "//pkg/tcpip/header",
- "//pkg/tcpip/seqnum",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_go_test(
- name = "tcp_user_timeout",
- srcs = ["tcp_user_timeout_test.go"],
- deps = [
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_go_test(
- name = "icmpv6_param_problem",
- srcs = ["icmpv6_param_problem_test.go"],
- # TODO(b/153485026): Fix netstack then remove the line below.
- netstack = False,
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-sh_binary(
- name = "test_runner",
- srcs = ["test_runner.sh"],
-)
diff --git a/test/packetimpact/tests/defs.bzl b/test/packetimpact/tests/defs.bzl
deleted file mode 100644
index 27c5de375..000000000
--- a/test/packetimpact/tests/defs.bzl
+++ /dev/null
@@ -1,137 +0,0 @@
-"""Defines rules for packetimpact test targets."""
-
-load("//tools:defs.bzl", "go_test")
-
-def _packetimpact_test_impl(ctx):
- test_runner = ctx.executable._test_runner
- bench = ctx.actions.declare_file("%s-bench" % ctx.label.name)
- bench_content = "\n".join([
- "#!/bin/bash",
- # This test will run part in a distinct user namespace. This can cause
- # permission problems, because all runfiles may not be owned by the
- # current user, and no other users will be mapped in that namespace.
- # Make sure that everything is readable here.
- "find . -type f -exec chmod a+rx {} \\;",
- "find . -type d -exec chmod a+rx {} \\;",
- "%s %s --posix_server_binary %s --testbench_binary %s $@\n" % (
- test_runner.short_path,
- " ".join(ctx.attr.flags),
- ctx.files._posix_server_binary[0].short_path,
- ctx.files.testbench_binary[0].short_path,
- ),
- ])
- ctx.actions.write(bench, bench_content, is_executable = True)
-
- transitive_files = depset()
- if hasattr(ctx.attr._test_runner, "data_runfiles"):
- transitive_files = depset(ctx.attr._test_runner.data_runfiles.files)
- runfiles = ctx.runfiles(
- files = [test_runner] + ctx.files.testbench_binary + ctx.files._posix_server_binary,
- transitive_files = transitive_files,
- collect_default = True,
- collect_data = True,
- )
- return [DefaultInfo(executable = bench, runfiles = runfiles)]
-
-_packetimpact_test = rule(
- attrs = {
- "_test_runner": attr.label(
- executable = True,
- cfg = "target",
- default = ":test_runner",
- ),
- "_posix_server_binary": attr.label(
- cfg = "target",
- default = "//test/packetimpact/dut:posix_server",
- ),
- "testbench_binary": attr.label(
- cfg = "target",
- mandatory = True,
- ),
- "flags": attr.string_list(
- mandatory = False,
- default = [],
- ),
- },
- test = True,
- implementation = _packetimpact_test_impl,
-)
-
-PACKETIMPACT_TAGS = ["local", "manual"]
-
-def packetimpact_linux_test(
- name,
- testbench_binary,
- expect_failure = False,
- **kwargs):
- """Add a packetimpact test on linux.
-
- Args:
- name: name of the test
- testbench_binary: the testbench binary
- **kwargs: all the other args, forwarded to _packetimpact_test
- """
- expect_failure_flag = ["--expect_failure"] if expect_failure else []
- _packetimpact_test(
- name = name + "_linux_test",
- testbench_binary = testbench_binary,
- flags = ["--dut_platform", "linux"] + expect_failure_flag,
- tags = PACKETIMPACT_TAGS + ["packetimpact"],
- **kwargs
- )
-
-def packetimpact_netstack_test(
- name,
- testbench_binary,
- expect_failure = False,
- **kwargs):
- """Add a packetimpact test on netstack.
-
- Args:
- name: name of the test
- testbench_binary: the testbench binary
- expect_failure: the test must fail
- **kwargs: all the other args, forwarded to _packetimpact_test
- """
- expect_failure_flag = []
- if expect_failure:
- expect_failure_flag = ["--expect_failure"]
- _packetimpact_test(
- name = name + "_netstack_test",
- testbench_binary = testbench_binary,
- # This is the default runtime unless
- # "--test_arg=--runtime=OTHER_RUNTIME" is used to override the value.
- flags = ["--dut_platform", "netstack", "--runtime=runsc-d"] + expect_failure_flag,
- tags = PACKETIMPACT_TAGS + ["packetimpact"],
- **kwargs
- )
-
-def packetimpact_go_test(name, size = "small", pure = True, linux = True, netstack = True, **kwargs):
- """Add packetimpact tests written in go.
-
- Args:
- name: name of the test
- size: size of the test
- pure: make a static go binary
- linux: generate a linux test
- netstack: generate a netstack test
- **kwargs: all the other args, forwarded to go_test
- """
- testbench_binary = name + "_test"
- go_test(
- name = testbench_binary,
- size = size,
- pure = pure,
- tags = PACKETIMPACT_TAGS,
- **kwargs
- )
- packetimpact_linux_test(
- name = name,
- expect_failure = not linux,
- testbench_binary = testbench_binary,
- )
- packetimpact_netstack_test(
- name = name,
- expect_failure = not netstack,
- testbench_binary = testbench_binary,
- )
diff --git a/test/packetimpact/tests/fin_wait2_timeout_test.go b/test/packetimpact/tests/fin_wait2_timeout_test.go
deleted file mode 100644
index 99dc77f9a..000000000
--- a/test/packetimpact/tests/fin_wait2_timeout_test.go
+++ /dev/null
@@ -1,70 +0,0 @@
-// 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 fin_wait2_timeout_test
-
-import (
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- tb "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func TestFinWait2Timeout(t *testing.T) {
- for _, tt := range []struct {
- description string
- linger2 bool
- }{
- {"WithLinger2", true},
- {"WithoutLinger2", false},
- } {
- t.Run(tt.description, func(t *testing.T) {
- dut := tb.NewDUT(t)
- defer dut.TearDown()
- listenFd, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(listenFd)
- conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort}, tb.TCP{SrcPort: &remotePort})
- defer conn.Close()
- conn.Handshake()
-
- acceptFd, _ := dut.Accept(listenFd)
- if tt.linger2 {
- tv := unix.Timeval{Sec: 1, Usec: 0}
- dut.SetSockOptTimeval(acceptFd, unix.SOL_TCP, unix.TCP_LINGER2, &tv)
- }
- dut.Close(acceptFd)
-
- if _, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil {
- t.Fatalf("expected a FIN-ACK within 1 second but got none: %s", err)
- }
- conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)})
-
- time.Sleep(5 * time.Second)
- conn.Drain()
-
- conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)})
- if tt.linger2 {
- if _, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagRst)}, time.Second); err != nil {
- t.Fatalf("expected a RST packet within a second but got none: %s", err)
- }
- } else {
- if got, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagRst)}, 10*time.Second); got != nil || err == nil {
- t.Fatalf("expected no RST packets within ten seconds but got one: %s", got)
- }
- }
- })
- }
-}
diff --git a/test/packetimpact/tests/icmpv6_param_problem_test.go b/test/packetimpact/tests/icmpv6_param_problem_test.go
deleted file mode 100644
index b48e55df4..000000000
--- a/test/packetimpact/tests/icmpv6_param_problem_test.go
+++ /dev/null
@@ -1,73 +0,0 @@
-// 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 icmpv6_param_problem_test
-
-import (
- "encoding/binary"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/pkg/tcpip/header"
- tb "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-// TestICMPv6ParamProblemTest sends a packet with a bad next header. The DUT
-// should respond with an ICMPv6 Parameter Problem message.
-func TestICMPv6ParamProblemTest(t *testing.T) {
- dut := tb.NewDUT(t)
- defer dut.TearDown()
- conn := tb.NewIPv6Conn(t, tb.IPv6{}, tb.IPv6{})
- defer conn.Close()
- ipv6 := tb.IPv6{
- // 254 is reserved and used for experimentation and testing. This should
- // cause an error.
- NextHeader: tb.Uint8(254),
- }
- icmpv6 := tb.ICMPv6{
- Type: tb.ICMPv6Type(header.ICMPv6EchoRequest),
- NDPPayload: []byte("hello world"),
- }
-
- toSend := conn.CreateFrame(ipv6, &icmpv6)
- conn.SendFrame(toSend)
-
- // Build the expected ICMPv6 payload, which includes an index to the
- // problematic byte and also the problematic packet as described in
- // https://tools.ietf.org/html/rfc4443#page-12 .
- ipv6Sent := toSend[1:]
- expectedPayload, err := ipv6Sent.ToBytes()
- if err != nil {
- t.Fatalf("can't convert %s to bytes: %s", ipv6Sent, err)
- }
-
- // The problematic field is the NextHeader.
- b := make([]byte, 4)
- binary.BigEndian.PutUint32(b, header.IPv6NextHeaderOffset)
- expectedPayload = append(b, expectedPayload...)
- expectedICMPv6 := tb.ICMPv6{
- Type: tb.ICMPv6Type(header.ICMPv6ParamProblem),
- NDPPayload: expectedPayload,
- }
-
- paramProblem := tb.Layers{
- &tb.Ether{},
- &tb.IPv6{},
- &expectedICMPv6,
- }
- timeout := time.Second
- if _, err := conn.ExpectFrame(paramProblem, timeout); err != nil {
- t.Errorf("expected %s within %s but got none: %s", paramProblem, timeout, err)
- }
-}
diff --git a/test/packetimpact/tests/tcp_close_wait_ack_test.go b/test/packetimpact/tests/tcp_close_wait_ack_test.go
deleted file mode 100644
index eb4cc7a65..000000000
--- a/test/packetimpact/tests/tcp_close_wait_ack_test.go
+++ /dev/null
@@ -1,102 +0,0 @@
-// 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 tcp_close_wait_ack_test
-
-import (
- "fmt"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/seqnum"
- tb "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func TestCloseWaitAck(t *testing.T) {
- for _, tt := range []struct {
- description string
- makeTestingTCP func(conn *tb.TCPIPv4, seqNumOffset seqnum.Size) tb.TCP
- seqNumOffset seqnum.Size
- expectAck bool
- }{
- {"OTW", GenerateOTWSeqSegment, 0, false},
- {"OTW", GenerateOTWSeqSegment, 1, true},
- {"OTW", GenerateOTWSeqSegment, 2, true},
- {"ACK", GenerateUnaccACKSegment, 0, false},
- {"ACK", GenerateUnaccACKSegment, 1, true},
- {"ACK", GenerateUnaccACKSegment, 2, true},
- } {
- t.Run(fmt.Sprintf("%s%d", tt.description, tt.seqNumOffset), func(t *testing.T) {
- dut := tb.NewDUT(t)
- defer dut.TearDown()
- listenFd, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(listenFd)
- conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort}, tb.TCP{SrcPort: &remotePort})
- defer conn.Close()
-
- conn.Handshake()
- acceptFd, _ := dut.Accept(listenFd)
-
- // Send a FIN to DUT to intiate the active close
- conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck | header.TCPFlagFin)})
- if _, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)}, time.Second); err != nil {
- t.Fatalf("expected an ACK for our fin and DUT should enter CLOSE_WAIT: %s", err)
- }
-
- // Send a segment with OTW Seq / unacc ACK and expect an ACK back
- conn.Send(tt.makeTestingTCP(&conn, tt.seqNumOffset), &tb.Payload{Bytes: []byte("Sample Data")})
- gotAck, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)}, time.Second)
- if tt.expectAck && err != nil {
- t.Fatalf("expected an ack but got none: %s", err)
- }
- if !tt.expectAck && gotAck != nil {
- t.Fatalf("expected no ack but got one: %s", gotAck)
- }
-
- // Now let's verify DUT is indeed in CLOSE_WAIT
- dut.Close(acceptFd)
- if _, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck | header.TCPFlagFin)}, time.Second); err != nil {
- t.Fatalf("expected DUT to send a FIN: %s", err)
- }
- // Ack the FIN from DUT
- conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)})
- // Send some extra data to DUT
- conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)}, &tb.Payload{Bytes: []byte("Sample Data")})
- if _, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagRst)}, time.Second); err != nil {
- t.Fatalf("expected DUT to send an RST: %s", err)
- }
- })
- }
-}
-
-// This generates an segment with seqnum = RCV.NXT + RCV.WND + seqNumOffset, the
-// generated segment is only acceptable when seqNumOffset is 0, otherwise an ACK
-// is expected from the receiver.
-func GenerateOTWSeqSegment(conn *tb.TCPIPv4, seqNumOffset seqnum.Size) tb.TCP {
- windowSize := seqnum.Size(*conn.SynAck().WindowSize)
- lastAcceptable := conn.LocalSeqNum().Add(windowSize - 1)
- otwSeq := uint32(lastAcceptable.Add(seqNumOffset))
- return tb.TCP{SeqNum: tb.Uint32(otwSeq), Flags: tb.Uint8(header.TCPFlagAck)}
-}
-
-// This generates an segment with acknum = SND.NXT + seqNumOffset, the generated
-// segment is only acceptable when seqNumOffset is 0, otherwise an ACK is
-// expected from the receiver.
-func GenerateUnaccACKSegment(conn *tb.TCPIPv4, seqNumOffset seqnum.Size) tb.TCP {
- lastAcceptable := conn.RemoteSeqNum()
- unaccAck := uint32(lastAcceptable.Add(seqNumOffset))
- return tb.TCP{AckNum: tb.Uint32(unaccAck), Flags: tb.Uint8(header.TCPFlagAck)}
-}
diff --git a/test/packetimpact/tests/tcp_noaccept_close_rst_test.go b/test/packetimpact/tests/tcp_noaccept_close_rst_test.go
deleted file mode 100644
index 7ebdd1950..000000000
--- a/test/packetimpact/tests/tcp_noaccept_close_rst_test.go
+++ /dev/null
@@ -1,37 +0,0 @@
-// 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 tcp_noaccept_close_rst_test
-
-import (
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- tb "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func TestTcpNoAcceptCloseReset(t *testing.T) {
- dut := tb.NewDUT(t)
- defer dut.TearDown()
- listenFd, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort}, tb.TCP{SrcPort: &remotePort})
- conn.Handshake()
- defer conn.Close()
- dut.Close(listenFd)
- if _, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagRst | header.TCPFlagAck)}, 1*time.Second); err != nil {
- t.Fatalf("expected a RST-ACK packet but got none: %s", err)
- }
-}
diff --git a/test/packetimpact/tests/tcp_outside_the_window_test.go b/test/packetimpact/tests/tcp_outside_the_window_test.go
deleted file mode 100644
index db3d3273b..000000000
--- a/test/packetimpact/tests/tcp_outside_the_window_test.go
+++ /dev/null
@@ -1,88 +0,0 @@
-// 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 tcp_outside_the_window_test
-
-import (
- "fmt"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/seqnum"
- tb "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-// TestTCPOutsideTheWindows tests the behavior of the DUT when packets arrive
-// that are inside or outside the TCP window. Packets that are outside the
-// window should force an extra ACK, as described in RFC793 page 69:
-// https://tools.ietf.org/html/rfc793#page-69
-func TestTCPOutsideTheWindow(t *testing.T) {
- for _, tt := range []struct {
- description string
- tcpFlags uint8
- payload []tb.Layer
- seqNumOffset seqnum.Size
- expectACK bool
- }{
- {"SYN", header.TCPFlagSyn, nil, 0, true},
- {"SYNACK", header.TCPFlagSyn | header.TCPFlagAck, nil, 0, true},
- {"ACK", header.TCPFlagAck, nil, 0, false},
- {"FIN", header.TCPFlagFin, nil, 0, false},
- {"Data", header.TCPFlagAck, []tb.Layer{&tb.Payload{Bytes: []byte("abc123")}}, 0, true},
-
- {"SYN", header.TCPFlagSyn, nil, 1, true},
- {"SYNACK", header.TCPFlagSyn | header.TCPFlagAck, nil, 1, true},
- {"ACK", header.TCPFlagAck, nil, 1, true},
- {"FIN", header.TCPFlagFin, nil, 1, false},
- {"Data", header.TCPFlagAck, []tb.Layer{&tb.Payload{Bytes: []byte("abc123")}}, 1, true},
-
- {"SYN", header.TCPFlagSyn, nil, 2, true},
- {"SYNACK", header.TCPFlagSyn | header.TCPFlagAck, nil, 2, true},
- {"ACK", header.TCPFlagAck, nil, 2, true},
- {"FIN", header.TCPFlagFin, nil, 2, false},
- {"Data", header.TCPFlagAck, []tb.Layer{&tb.Payload{Bytes: []byte("abc123")}}, 2, true},
- } {
- t.Run(fmt.Sprintf("%s%d", tt.description, tt.seqNumOffset), func(t *testing.T) {
- dut := tb.NewDUT(t)
- defer dut.TearDown()
- listenFD, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(listenFD)
- conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort}, tb.TCP{SrcPort: &remotePort})
- defer conn.Close()
- conn.Handshake()
- acceptFD, _ := dut.Accept(listenFD)
- defer dut.Close(acceptFD)
-
- windowSize := seqnum.Size(*conn.SynAck().WindowSize) + tt.seqNumOffset
- conn.Drain()
- // Ignore whatever incrementing that this out-of-order packet might cause
- // to the AckNum.
- localSeqNum := tb.Uint32(uint32(*conn.LocalSeqNum()))
- conn.Send(tb.TCP{
- Flags: tb.Uint8(tt.tcpFlags),
- SeqNum: tb.Uint32(uint32(conn.LocalSeqNum().Add(windowSize))),
- }, tt.payload...)
- timeout := 3 * time.Second
- gotACK, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), AckNum: localSeqNum}, timeout)
- if tt.expectACK && err != nil {
- t.Fatalf("expected an ACK packet within %s but got none: %s", timeout, err)
- }
- if !tt.expectACK && gotACK != nil {
- t.Fatalf("expected no ACK packet within %s but got one: %s", timeout, gotACK)
- }
- })
- }
-}
diff --git a/test/packetimpact/tests/tcp_should_piggyback_test.go b/test/packetimpact/tests/tcp_should_piggyback_test.go
deleted file mode 100644
index b0be6ba23..000000000
--- a/test/packetimpact/tests/tcp_should_piggyback_test.go
+++ /dev/null
@@ -1,59 +0,0 @@
-// 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 tcp_should_piggyback_test
-
-import (
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- tb "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func TestPiggyback(t *testing.T) {
- dut := tb.NewDUT(t)
- defer dut.TearDown()
- listenFd, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(listenFd)
- conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort, WindowSize: tb.Uint16(12)}, tb.TCP{SrcPort: &remotePort})
- defer conn.Close()
-
- conn.Handshake()
- acceptFd, _ := dut.Accept(listenFd)
- defer dut.Close(acceptFd)
-
- dut.SetSockOptInt(acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
-
- sampleData := []byte("Sample Data")
-
- dut.Send(acceptFd, sampleData, 0)
- expectedTCP := tb.TCP{Flags: tb.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}
- expectedPayload := tb.Payload{Bytes: sampleData}
- if _, err := conn.ExpectData(&expectedTCP, &expectedPayload, time.Second); err != nil {
- t.Fatalf("Expected %v but didn't get one: %s", tb.Layers{&expectedTCP, &expectedPayload}, err)
- }
-
- // Cause DUT to send us more data as soon as we ACK their first data segment because we have
- // a small window.
- dut.Send(acceptFd, sampleData, 0)
-
- // DUT should ACK our segment by piggybacking ACK to their outstanding data segment instead of
- // sending a separate ACK packet.
- conn.Send(expectedTCP, &expectedPayload)
- if _, err := conn.ExpectData(&expectedTCP, &expectedPayload, time.Second); err != nil {
- t.Fatalf("Expected %v but didn't get one: %s", tb.Layers{&expectedTCP, &expectedPayload}, err)
- }
-}
diff --git a/test/packetimpact/tests/tcp_user_timeout_test.go b/test/packetimpact/tests/tcp_user_timeout_test.go
deleted file mode 100644
index 3cf82badb..000000000
--- a/test/packetimpact/tests/tcp_user_timeout_test.go
+++ /dev/null
@@ -1,100 +0,0 @@
-// 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 tcp_user_timeout_test
-
-import (
- "fmt"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- tb "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func sendPayload(conn *tb.TCPIPv4, dut *tb.DUT, fd int32) error {
- sampleData := make([]byte, 100)
- for i := range sampleData {
- sampleData[i] = uint8(i)
- }
- conn.Drain()
- dut.Send(fd, sampleData, 0)
- if _, err := conn.ExpectData(&tb.TCP{Flags: tb.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, &tb.Payload{Bytes: sampleData}, time.Second); err != nil {
- return fmt.Errorf("expected data but got none: %w", err)
- }
- return nil
-}
-
-func sendFIN(conn *tb.TCPIPv4, dut *tb.DUT, fd int32) error {
- dut.Close(fd)
- return nil
-}
-
-func TestTCPUserTimeout(t *testing.T) {
- for _, tt := range []struct {
- description string
- userTimeout time.Duration
- sendDelay time.Duration
- }{
- {"NoUserTimeout", 0, 3 * time.Second},
- {"ACKBeforeUserTimeout", 5 * time.Second, 4 * time.Second},
- {"ACKAfterUserTimeout", 5 * time.Second, 7 * time.Second},
- } {
- for _, ttf := range []struct {
- description string
- f func(conn *tb.TCPIPv4, dut *tb.DUT, fd int32) error
- }{
- {"AfterPayload", sendPayload},
- {"AfterFIN", sendFIN},
- } {
- t.Run(tt.description+ttf.description, func(t *testing.T) {
- // Create a socket, listen, TCP handshake, and accept.
- dut := tb.NewDUT(t)
- defer dut.TearDown()
- listenFD, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(listenFD)
- conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort}, tb.TCP{SrcPort: &remotePort})
- defer conn.Close()
- conn.Handshake()
- acceptFD, _ := dut.Accept(listenFD)
-
- if tt.userTimeout != 0 {
- dut.SetSockOptInt(acceptFD, unix.SOL_TCP, unix.TCP_USER_TIMEOUT, int32(tt.userTimeout.Milliseconds()))
- }
-
- if err := ttf.f(&conn, &dut, acceptFD); err != nil {
- t.Fatal(err)
- }
-
- time.Sleep(tt.sendDelay)
- conn.Drain()
- conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)})
-
- // If TCP_USER_TIMEOUT was set and the above delay was longer than the
- // TCP_USER_TIMEOUT then the DUT should send a RST in response to the
- // testbench's packet.
- expectRST := tt.userTimeout != 0 && tt.sendDelay > tt.userTimeout
- expectTimeout := 5 * time.Second
- got, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagRst)}, expectTimeout)
- if expectRST && err != nil {
- t.Errorf("expected RST packet within %s but got none: %s", expectTimeout, err)
- }
- if !expectRST && got != nil {
- t.Errorf("expected no RST packet within %s but got one: %s", expectTimeout, got)
- }
- })
- }
- }
-}
diff --git a/test/packetimpact/tests/tcp_window_shrink_test.go b/test/packetimpact/tests/tcp_window_shrink_test.go
deleted file mode 100644
index c9354074e..000000000
--- a/test/packetimpact/tests/tcp_window_shrink_test.go
+++ /dev/null
@@ -1,68 +0,0 @@
-// 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 tcp_window_shrink_test
-
-import (
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- tb "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func TestWindowShrink(t *testing.T) {
- dut := tb.NewDUT(t)
- defer dut.TearDown()
- listenFd, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(listenFd)
- conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort}, tb.TCP{SrcPort: &remotePort})
- defer conn.Close()
-
- conn.Handshake()
- acceptFd, _ := dut.Accept(listenFd)
- defer dut.Close(acceptFd)
-
- dut.SetSockOptInt(acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
-
- sampleData := []byte("Sample Data")
- samplePayload := &tb.Payload{Bytes: sampleData}
-
- dut.Send(acceptFd, sampleData, 0)
- if _, err := conn.ExpectData(&tb.TCP{}, samplePayload, time.Second); err != nil {
- t.Fatalf("expected a packet with payload %v: %s", samplePayload, err)
- }
- conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)})
-
- dut.Send(acceptFd, sampleData, 0)
- dut.Send(acceptFd, sampleData, 0)
- if _, err := conn.ExpectData(&tb.TCP{}, samplePayload, time.Second); err != nil {
- t.Fatalf("expected a packet with payload %v: %s", samplePayload, err)
- }
- if _, err := conn.ExpectData(&tb.TCP{}, samplePayload, time.Second); err != nil {
- t.Fatalf("expected a packet with payload %v: %s", samplePayload, err)
- }
- // We close our receiving window here
- conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), WindowSize: tb.Uint16(0)})
-
- dut.Send(acceptFd, []byte("Sample Data"), 0)
- // Note: There is another kind of zero-window probing which Windows uses (by sending one
- // new byte at `RemoteSeqNum`), if netstack wants to go that way, we may want to change
- // the following lines.
- expectedRemoteSeqNum := *conn.RemoteSeqNum() - 1
- if _, err := conn.ExpectData(&tb.TCP{SeqNum: tb.Uint32(uint32(expectedRemoteSeqNum))}, nil, time.Second); err != nil {
- t.Fatalf("expected a packet with sequence number %v: %s", expectedRemoteSeqNum, err)
- }
-}
diff --git a/test/packetimpact/tests/test_runner.sh b/test/packetimpact/tests/test_runner.sh
deleted file mode 100755
index 706441cce..000000000
--- a/test/packetimpact/tests/test_runner.sh
+++ /dev/null
@@ -1,325 +0,0 @@
-#!/bin/bash
-
-# 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.
-
-# Run a packetimpact test. Two docker containers are made, one for the
-# Device-Under-Test (DUT) and one for the test bench. Each is attached with
-# two networks, one for control packets that aid the test and one for test
-# packets which are sent as part of the test and observed for correctness.
-
-set -euxo pipefail
-
-function failure() {
- local lineno=$1
- local msg=$2
- local filename="$0"
- echo "FAIL: $filename:$lineno: $msg"
-}
-trap 'failure ${LINENO} "$BASH_COMMAND"' ERR
-
-declare -r LONGOPTS="dut_platform:,posix_server_binary:,testbench_binary:,runtime:,tshark,extra_test_arg:,expect_failure"
-
-# Don't use declare below so that the error from getopt will end the script.
-PARSED=$(getopt --options "" --longoptions=$LONGOPTS --name "$0" -- "$@")
-
-eval set -- "$PARSED"
-
-declare -a EXTRA_TEST_ARGS
-
-while true; do
- case "$1" in
- --dut_platform)
- # Either "linux" or "netstack".
- declare -r DUT_PLATFORM="$2"
- shift 2
- ;;
- --posix_server_binary)
- declare -r POSIX_SERVER_BINARY="$2"
- shift 2
- ;;
- --testbench_binary)
- declare -r TESTBENCH_BINARY="$2"
- shift 2
- ;;
- --runtime)
- # Not readonly because there might be multiple --runtime arguments and we
- # want to use just the last one. Only used if --dut_platform is
- # "netstack".
- declare RUNTIME="$2"
- shift 2
- ;;
- --tshark)
- declare -r TSHARK="1"
- shift 1
- ;;
- --extra_test_arg)
- EXTRA_TEST_ARGS+="$2"
- shift 2
- ;;
- --expect_failure)
- declare -r EXPECT_FAILURE="1"
- shift 1
- ;;
- --)
- shift
- break
- ;;
- *)
- echo "Programming error"
- exit 3
- esac
-done
-
-# All the other arguments are scripts.
-declare -r scripts="$@"
-
-# Check that the required flags are defined in a way that is safe for "set -u".
-if [[ "${DUT_PLATFORM-}" == "netstack" ]]; then
- if [[ -z "${RUNTIME-}" ]]; then
- echo "FAIL: Missing --runtime argument: ${RUNTIME-}"
- exit 2
- fi
- declare -r RUNTIME_ARG="--runtime ${RUNTIME}"
-elif [[ "${DUT_PLATFORM-}" == "linux" ]]; then
- declare -r RUNTIME_ARG=""
-else
- echo "FAIL: Bad or missing --dut_platform argument: ${DUT_PLATFORM-}"
- exit 2
-fi
-if [[ ! -f "${POSIX_SERVER_BINARY-}" ]]; then
- echo "FAIL: Bad or missing --posix_server_binary: ${POSIX_SERVER-}"
- exit 2
-fi
-if [[ ! -f "${TESTBENCH_BINARY-}" ]]; then
- echo "FAIL: Bad or missing --testbench_binary: ${TESTBENCH_BINARY-}"
- exit 2
-fi
-
-function new_net_prefix() {
- # Class C, 192.0.0.0 to 223.255.255.255, transitionally has mask 24.
- echo "$(shuf -i 192-223 -n 1).$(shuf -i 0-255 -n 1).$(shuf -i 0-255 -n 1)"
-}
-
-# Variables specific to the control network and interface start with CTRL_.
-# Variables specific to the test network and interface start with TEST_.
-# Variables specific to the DUT start with DUT_.
-# Variables specific to the test bench start with TESTBENCH_.
-# Use random numbers so that test networks don't collide.
-declare CTRL_NET="ctrl_net-${RANDOM}${RANDOM}"
-declare CTRL_NET_PREFIX=$(new_net_prefix)
-declare TEST_NET="test_net-${RANDOM}${RANDOM}"
-declare TEST_NET_PREFIX=$(new_net_prefix)
-# On both DUT and test bench, testing packets are on the eth2 interface.
-declare -r TEST_DEVICE="eth2"
-# Number of bits in the *_NET_PREFIX variables.
-declare -r NET_MASK="24"
-# Last bits of the DUT's IP address.
-declare -r DUT_NET_SUFFIX=".10"
-# Control port.
-declare -r CTRL_PORT="40000"
-# Last bits of the test bench's IP address.
-declare -r TESTBENCH_NET_SUFFIX=".20"
-declare -r TIMEOUT="60"
-declare -r IMAGE_TAG="gcr.io/gvisor-presubmit/packetimpact"
-
-# Make sure that docker is installed.
-docker --version
-
-function finish {
- local cleanup_success=1
-
- if [[ -z "${TSHARK-}" ]]; then
- # Kill tcpdump so that it will flush output.
- docker exec -t "${TESTBENCH}" \
- killall tcpdump || \
- cleanup_success=0
- else
- # Kill tshark so that it will flush output.
- docker exec -t "${TESTBENCH}" \
- killall tshark || \
- cleanup_success=0
- fi
-
- for net in "${CTRL_NET}" "${TEST_NET}"; do
- # Kill all processes attached to ${net}.
- for docker_command in "kill" "rm"; do
- (docker network inspect "${net}" \
- --format '{{range $key, $value := .Containers}}{{$key}} {{end}}' \
- | xargs -r docker "${docker_command}") || \
- cleanup_success=0
- done
- # Remove the network.
- docker network rm "${net}" || \
- cleanup_success=0
- done
-
- if ((!$cleanup_success)); then
- echo "FAIL: Cleanup command failed"
- exit 4
- fi
-}
-trap finish EXIT
-
-# Subnet for control packets between test bench and DUT.
-while ! docker network create \
- "--subnet=${CTRL_NET_PREFIX}.0/${NET_MASK}" "${CTRL_NET}"; do
- sleep 0.1
- CTRL_NET_PREFIX=$(new_net_prefix)
- CTRL_NET="ctrl_net-${RANDOM}${RANDOM}"
-done
-
-# Subnet for the packets that are part of the test.
-while ! docker network create \
- "--subnet=${TEST_NET_PREFIX}.0/${NET_MASK}" "${TEST_NET}"; do
- sleep 0.1
- TEST_NET_PREFIX=$(new_net_prefix)
- TEST_NET="test_net-${RANDOM}${RANDOM}"
-done
-
-docker pull "${IMAGE_TAG}"
-
-# Create the DUT container and connect to network.
-DUT=$(docker create ${RUNTIME_ARG} --privileged --rm \
- --cap-add NET_ADMIN \
- --sysctl net.ipv6.conf.all.disable_ipv6=0 \
- --stop-timeout ${TIMEOUT} -it ${IMAGE_TAG})
-docker network connect "${CTRL_NET}" \
- --ip "${CTRL_NET_PREFIX}${DUT_NET_SUFFIX}" "${DUT}" \
- || (docker kill ${DUT}; docker rm ${DUT}; false)
-docker network connect "${TEST_NET}" \
- --ip "${TEST_NET_PREFIX}${DUT_NET_SUFFIX}" "${DUT}" \
- || (docker kill ${DUT}; docker rm ${DUT}; false)
-docker start "${DUT}"
-
-# Create the test bench container and connect to network.
-TESTBENCH=$(docker create --privileged --rm \
- --cap-add NET_ADMIN \
- --sysctl net.ipv6.conf.all.disable_ipv6=0 \
- --stop-timeout ${TIMEOUT} -it ${IMAGE_TAG})
-docker network connect "${CTRL_NET}" \
- --ip "${CTRL_NET_PREFIX}${TESTBENCH_NET_SUFFIX}" "${TESTBENCH}" \
- || (docker kill ${TESTBENCH}; docker rm ${TESTBENCH}; false)
-docker network connect "${TEST_NET}" \
- --ip "${TEST_NET_PREFIX}${TESTBENCH_NET_SUFFIX}" "${TESTBENCH}" \
- || (docker kill ${TESTBENCH}; docker rm ${TESTBENCH}; false)
-docker start "${TESTBENCH}"
-
-# Start the posix_server in the DUT.
-declare -r DOCKER_POSIX_SERVER_BINARY="/$(basename ${POSIX_SERVER_BINARY})"
-docker cp -L ${POSIX_SERVER_BINARY} "${DUT}:${DOCKER_POSIX_SERVER_BINARY}"
-
-docker exec -t "${DUT}" \
- /bin/bash -c "${DOCKER_POSIX_SERVER_BINARY} \
- --ip ${CTRL_NET_PREFIX}${DUT_NET_SUFFIX} \
- --port ${CTRL_PORT}" &
-
-# Because the Linux kernel receives the SYN-ACK but didn't send the SYN it will
-# issue a RST. To prevent this IPtables can be used to filter those out.
-docker exec "${TESTBENCH}" \
- iptables -A INPUT -i ${TEST_DEVICE} -j DROP
-
-# Wait for the DUT server to come up. Attempt to connect to it from the test
-# bench every 100 milliseconds until success.
-while ! docker exec "${TESTBENCH}" \
- nc -zv "${CTRL_NET_PREFIX}${DUT_NET_SUFFIX}" "${CTRL_PORT}"; do
- sleep 0.1
-done
-
-declare -r REMOTE_MAC=$(docker exec -t "${DUT}" ip link show \
- "${TEST_DEVICE}" | tail -1 | cut -d' ' -f6)
-declare -r LOCAL_MAC=$(docker exec -t "${TESTBENCH}" ip link show \
- "${TEST_DEVICE}" | tail -1 | cut -d' ' -f6)
-declare REMOTE_IPV6=$(docker exec -t "${DUT}" ip addr show scope link \
- "${TEST_DEVICE}" | grep inet6 | cut -d' ' -f6 | cut -d'/' -f1)
-declare -r LOCAL_IPV6=$(docker exec -t "${TESTBENCH}" ip addr show scope link \
- "${TEST_DEVICE}" | grep inet6 | cut -d' ' -f6 | cut -d'/' -f1)
-
-# Netstack as DUT doesn't assign IPv6 addresses automatically so do it if
-# needed. Convert the MAC address to an IPv6 link local address as described in
-# RFC 4291 page 20: https://tools.ietf.org/html/rfc4291#page-20
-if [[ -z "${REMOTE_IPV6}" ]]; then
- # Split the octets of the MAC into an array of strings.
- IFS=":" read -a REMOTE_OCTETS <<< "${REMOTE_MAC}"
- # Flip the global bit.
- REMOTE_OCTETS[0]=$(printf '%x' "$((0x${REMOTE_OCTETS[0]} ^ 2))")
- # Add the IPv6 address.
- docker exec "${DUT}" \
- ip addr add $(printf 'fe80::%02x%02x:%02xff:fe%02x:%02x%02x/64' \
- "0x${REMOTE_OCTETS[0]}" "0x${REMOTE_OCTETS[1]}" "0x${REMOTE_OCTETS[2]}" \
- "0x${REMOTE_OCTETS[3]}" "0x${REMOTE_OCTETS[4]}" "0x${REMOTE_OCTETS[5]}") \
- scope link \
- dev "${TEST_DEVICE}"
- # Re-extract the IPv6 address.
- # TODO(eyalsoha): Add "scope link" below when netstack supports correctly
- # creating link-local IPv6 addresses.
- REMOTE_IPV6=$(docker exec -t "${DUT}" ip addr show \
- "${TEST_DEVICE}" | grep inet6 | cut -d' ' -f6 | cut -d'/' -f1)
-fi
-
-declare -r DOCKER_TESTBENCH_BINARY="/$(basename ${TESTBENCH_BINARY})"
-docker cp -L "${TESTBENCH_BINARY}" "${TESTBENCH}:${DOCKER_TESTBENCH_BINARY}"
-
-if [[ -z "${TSHARK-}" ]]; then
- # Run tcpdump in the test bench unbuffered, without dns resolution, just on
- # the interface with the test packets.
- docker exec -t "${TESTBENCH}" \
- tcpdump -S -vvv -U -n -i "${TEST_DEVICE}" \
- net "${TEST_NET_PREFIX}/24" or \
- host "${REMOTE_IPV6}" or \
- host "${LOCAL_IPV6}" &
-else
- # Run tshark in the test bench unbuffered, without dns resolution, just on the
- # interface with the test packets.
- docker exec -t "${TESTBENCH}" \
- tshark -V -l -n -i "${TEST_DEVICE}" \
- -o tcp.check_checksum:TRUE \
- -o udp.check_checksum:TRUE \
- net "${TEST_NET_PREFIX}/24" or \
- host "${REMOTE_IPV6}" or \
- host "${LOCAL_IPV6}" &
-fi
-
-# tcpdump and tshark take time to startup
-sleep 3
-
-# Start a packetimpact test on the test bench. The packetimpact test sends and
-# receives packets and also sends POSIX socket commands to the posix_server to
-# be executed on the DUT.
-docker exec \
- -e XML_OUTPUT_FILE="/test.xml" \
- -e TEST_TARGET \
- -t "${TESTBENCH}" \
- /bin/bash -c "${DOCKER_TESTBENCH_BINARY} \
- ${EXTRA_TEST_ARGS[@]-} \
- --posix_server_ip=${CTRL_NET_PREFIX}${DUT_NET_SUFFIX} \
- --posix_server_port=${CTRL_PORT} \
- --remote_ipv4=${TEST_NET_PREFIX}${DUT_NET_SUFFIX} \
- --local_ipv4=${TEST_NET_PREFIX}${TESTBENCH_NET_SUFFIX} \
- --remote_ipv6=${REMOTE_IPV6} \
- --local_ipv6=${LOCAL_IPV6} \
- --remote_mac=${REMOTE_MAC} \
- --local_mac=${LOCAL_MAC} \
- --device=${TEST_DEVICE}" && true
-declare -r TEST_RESULT="${?}"
-if [[ -z "${EXPECT_FAILURE-}" && "${TEST_RESULT}" != 0 ]]; then
- echo 'FAIL: This test was expected to pass.'
- exit ${TEST_RESULT}
-fi
-if [[ ! -z "${EXPECT_FAILURE-}" && "${TEST_RESULT}" == 0 ]]; then
- echo 'FAIL: This test was expected to fail but passed. Enable the test and' \
- 'mark the corresponding bug as fixed.'
- exit 1
-fi
-echo PASS: No errors.
diff --git a/test/packetimpact/tests/udp_recv_multicast_test.go b/test/packetimpact/tests/udp_recv_multicast_test.go
deleted file mode 100644
index 61fd17050..000000000
--- a/test/packetimpact/tests/udp_recv_multicast_test.go
+++ /dev/null
@@ -1,37 +0,0 @@
-// 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 udp_recv_multicast_test
-
-import (
- "net"
- "testing"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip"
- tb "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func TestUDPRecvMulticast(t *testing.T) {
- dut := tb.NewDUT(t)
- defer dut.TearDown()
- boundFD, remotePort := dut.CreateBoundSocket(unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.ParseIP("0.0.0.0"))
- defer dut.Close(boundFD)
- conn := tb.NewUDPIPv4(t, tb.UDP{DstPort: &remotePort}, tb.UDP{SrcPort: &remotePort})
- defer conn.Close()
- frame := conn.CreateFrame(&tb.UDP{}, &tb.Payload{Bytes: []byte("hello world")})
- frame[1].(*tb.IPv4).DstAddr = tb.Address(tcpip.Address(net.ParseIP("224.0.0.1").To4()))
- conn.SendFrame(frame)
- dut.Recv(boundFD, 100, 0)
-}