summaryrefslogtreecommitdiffhomepage
path: root/test/packetimpact
diff options
context:
space:
mode:
Diffstat (limited to 'test/packetimpact')
-rw-r--r--test/packetimpact/README.md709
-rw-r--r--test/packetimpact/dut/BUILD30
-rw-r--r--test/packetimpact/dut/posix_server.cc464
-rw-r--r--test/packetimpact/netdevs/BUILD23
-rw-r--r--test/packetimpact/netdevs/netdevs.go122
-rw-r--r--test/packetimpact/netdevs/netdevs_test.go227
-rw-r--r--test/packetimpact/proto/BUILD12
-rw-r--r--test/packetimpact/proto/posix_server.proto268
-rw-r--r--test/packetimpact/runner/BUILD38
-rw-r--r--test/packetimpact/runner/defs.bzl315
-rw-r--r--test/packetimpact/runner/dut.go657
-rw-r--r--test/packetimpact/runner/packetimpact_test.go32
-rw-r--r--test/packetimpact/testbench/BUILD47
-rw-r--r--test/packetimpact/testbench/connections.go1263
-rw-r--r--test/packetimpact/testbench/dut.go789
-rw-r--r--test/packetimpact/testbench/dut_client.go31
-rw-r--r--test/packetimpact/testbench/layers.go1602
-rw-r--r--test/packetimpact/testbench/layers_test.go728
-rw-r--r--test/packetimpact/testbench/rawsockets.go207
-rw-r--r--test/packetimpact/testbench/testbench.go180
-rw-r--r--test/packetimpact/tests/BUILD431
-rw-r--r--test/packetimpact/tests/fin_wait2_timeout_test.go74
-rw-r--r--test/packetimpact/tests/generic_dgram_socket_send_recv_test.go781
-rw-r--r--test/packetimpact/tests/icmpv6_param_problem_test.go74
-rw-r--r--test/packetimpact/tests/ipv4_fragment_reassembly_test.go175
-rw-r--r--test/packetimpact/tests/ipv4_id_uniqueness_test.go121
-rw-r--r--test/packetimpact/tests/ipv6_fragment_icmp_error_test.go356
-rw-r--r--test/packetimpact/tests/ipv6_fragment_reassembly_test.go182
-rw-r--r--test/packetimpact/tests/ipv6_unknown_options_action_test.go183
-rw-r--r--test/packetimpact/tests/tcp_connect_icmp_error_test.go106
-rw-r--r--test/packetimpact/tests/tcp_cork_mss_test.go83
-rw-r--r--test/packetimpact/tests/tcp_fin_retransmission_test.go87
-rw-r--r--test/packetimpact/tests/tcp_handshake_window_size_test.go65
-rw-r--r--test/packetimpact/tests/tcp_info_test.go102
-rw-r--r--test/packetimpact/tests/tcp_linger_test.go265
-rw-r--r--test/packetimpact/tests/tcp_listen_backlog_test.go94
-rw-r--r--test/packetimpact/tests/tcp_network_unreachable_test.go144
-rw-r--r--test/packetimpact/tests/tcp_noaccept_close_rst_test.go46
-rw-r--r--test/packetimpact/tests/tcp_outside_the_window_test.go182
-rw-r--r--test/packetimpact/tests/tcp_paws_mechanism_test.go108
-rw-r--r--test/packetimpact/tests/tcp_queue_send_recv_in_syn_sent_test.go286
-rw-r--r--test/packetimpact/tests/tcp_rack_test.go404
-rw-r--r--test/packetimpact/tests/tcp_rcv_buf_space_test.go78
-rw-r--r--test/packetimpact/tests/tcp_retransmits_test.go94
-rw-r--r--test/packetimpact/tests/tcp_send_window_sizes_piggyback_test.go103
-rw-r--r--test/packetimpact/tests/tcp_syncookie_test.go151
-rw-r--r--test/packetimpact/tests/tcp_synrcvd_reset_test.go60
-rw-r--r--test/packetimpact/tests/tcp_synsent_reset_test.go94
-rw-r--r--test/packetimpact/tests/tcp_timewait_reset_test.go67
-rw-r--r--test/packetimpact/tests/tcp_unacc_seq_ack_test.go274
-rw-r--r--test/packetimpact/tests/tcp_user_timeout_test.go99
-rw-r--r--test/packetimpact/tests/tcp_window_shrink_test.go72
-rw-r--r--test/packetimpact/tests/tcp_zero_receive_window_test.go202
-rw-r--r--test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go115
-rw-r--r--test/packetimpact/tests/tcp_zero_window_probe_test.go111
-rw-r--r--test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go97
-rw-r--r--test/packetimpact/tests/udp_any_addr_recv_unicast_test.go50
-rw-r--r--test/packetimpact/tests/udp_discard_mcast_source_addr_test.go93
-rw-r--r--test/packetimpact/tests/udp_icmp_error_propagation_test.go352
59 files changed, 0 insertions, 14205 deletions
diff --git a/test/packetimpact/README.md b/test/packetimpact/README.md
deleted file mode 100644
index fe0976ba5..000000000
--- a/test/packetimpact/README.md
+++ /dev/null
@@ -1,709 +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.
-
-## How to run packetimpact tests?
-
-Build the test container image by running the following at the root of the
-repository:
-
-```bash
-$ make load-packetimpact
-```
-
-Run a test, e.g. `fin_wait2_timeout`, against Linux:
-
-```bash
-$ bazel test //test/packetimpact/tests:fin_wait2_timeout_native_test
-```
-
-Run the same test, but against gVisor:
-
-```bash
-$ bazel test //test/packetimpact/tests:fin_wait2_timeout_netstack_test
-```
-
-## 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)
-}
-```
-
-### Adding a new packetimpact test
-
-* Create a go test in the [tests directory](tests/)
-* Add a `packetimpact_testbench` rule in [BUILD](tests/BUILD)
-* Add the test into the `ALL_TESTS` list in [defs.bzl](runner/defs.bzl),
- otherwise you will see an error message complaining about a missing test.
-
-## 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 0be14ca3e..000000000
--- a/test/packetimpact/dut/BUILD
+++ /dev/null
@@ -1,30 +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",
- "@com_google_absl//absl/strings:str_format",
- ],
-)
-
-cc_binary(
- name = "posix_server_dynamic",
- srcs = ["posix_server.cc"],
- deps = [
- grpcpp,
- "//test/packetimpact/proto:posix_server_cc_grpc_proto",
- "//test/packetimpact/proto:posix_server_cc_proto",
- "@com_google_absl//absl/strings:str_format",
- ],
-)
diff --git a/test/packetimpact/dut/posix_server.cc b/test/packetimpact/dut/posix_server.cc
deleted file mode 100644
index 49f41c887..000000000
--- a/test/packetimpact/dut/posix_server.cc
+++ /dev/null
@@ -1,464 +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 <arpa/inet.h>
-#include <fcntl.h>
-#include <getopt.h>
-#include <netdb.h>
-#include <netinet/in.h>
-#include <poll.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <time.h>
-#include <unistd.h>
-
-#include <iostream>
-#include <unordered_map>
-
-#include "absl/strings/str_format.h"
-#include "include/grpcpp/security/server_credentials.h"
-#include "include/grpcpp/server_builder.h"
-#include "include/grpcpp/server_context.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);
- // sin6_scope_id is stored in host byte order.
- //
- // https://www.gnu.org/software/libc/manual/html_node/Internet-Address-Formats.html
- response_in6->set_scope_id(addr_in6->sin6_scope_id);
- return ::grpc::Status::OK;
- }
- }
- return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "Unknown Sockaddr");
-}
-
-::grpc::Status proto_to_sockaddr(const posix_server::Sockaddr &sockaddr_proto,
- sockaddr_storage *addr, socklen_t *addr_len) {
- switch (sockaddr_proto.sockaddr_case()) {
- case posix_server::Sockaddr::SockaddrCase::kIn: {
- auto proto_in = sockaddr_proto.in();
- if (proto_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 = proto_in.family();
- addr_in->sin_port = htons(proto_in.port());
- proto_in.addr().copy(reinterpret_cast<char *>(&addr_in->sin_addr.s_addr),
- 4);
- *addr_len = sizeof(*addr_in);
- break;
- }
- case posix_server::Sockaddr::SockaddrCase::kIn6: {
- auto proto_in6 = sockaddr_proto.in6();
- if (proto_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 = proto_in6.family();
- addr_in6->sin6_port = htons(proto_in6.port());
- addr_in6->sin6_flowinfo = htonl(proto_in6.flowinfo());
- proto_in6.addr().copy(
- reinterpret_cast<char *>(&addr_in6->sin6_addr.s6_addr), 16);
- // sin6_scope_id is stored in host byte order.
- //
- // https://www.gnu.org/software/libc/manual/html_node/Internet-Address-Formats.html
- addr_in6->sin6_scope_id = proto_in6.scope_id();
- *addr_len = sizeof(*addr_in6);
- break;
- }
- case posix_server::Sockaddr::SockaddrCase::SOCKADDR_NOT_SET:
- default:
- return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
- "Unknown Sockaddr");
- }
- return ::grpc::Status::OK;
-}
-
-class PosixImpl final : public posix_server::Posix::Service {
- ::grpc::Status Accept(grpc::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));
- if (response->fd() < 0) {
- response->set_errno_(errno);
- }
- return sockaddr_to_proto(addr, addrlen, response->mutable_addr());
- }
-
- ::grpc::Status Bind(grpc::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;
- socklen_t addr_len;
- auto err = proto_to_sockaddr(request->addr(), &addr, &addr_len);
- if (!err.ok()) {
- return err;
- }
-
- response->set_ret(
- bind(request->sockfd(), reinterpret_cast<sockaddr *>(&addr), addr_len));
- if (response->ret() < 0) {
- response->set_errno_(errno);
- }
- return ::grpc::Status::OK;
- }
-
- ::grpc::Status Close(grpc::ServerContext *context,
- const ::posix_server::CloseRequest *request,
- ::posix_server::CloseResponse *response) override {
- response->set_ret(close(request->fd()));
- if (response->ret() < 0) {
- response->set_errno_(errno);
- }
- return ::grpc::Status::OK;
- }
-
- ::grpc::Status Connect(grpc::ServerContext *context,
- const ::posix_server::ConnectRequest *request,
- ::posix_server::ConnectResponse *response) override {
- if (!request->has_addr()) {
- return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
- "Missing address");
- }
- sockaddr_storage addr;
- socklen_t addr_len;
- auto err = proto_to_sockaddr(request->addr(), &addr, &addr_len);
- if (!err.ok()) {
- return err;
- }
-
- response->set_ret(connect(request->sockfd(),
- reinterpret_cast<sockaddr *>(&addr), addr_len));
- if (response->ret() < 0) {
- response->set_errno_(errno);
- }
- return ::grpc::Status::OK;
- }
-
- ::grpc::Status GetSockName(
- grpc::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));
- if (response->ret() < 0) {
- response->set_errno_(errno);
- }
- return sockaddr_to_proto(addr, addrlen, response->mutable_addr());
- }
-
- ::grpc::Status GetSockOpt(
- grpc::ServerContext *context,
- const ::posix_server::GetSockOptRequest *request,
- ::posix_server::GetSockOptResponse *response) override {
- switch (request->type()) {
- case ::posix_server::GetSockOptRequest::BYTES: {
- socklen_t optlen = request->optlen();
- std::vector<char> buf(optlen);
- response->set_ret(::getsockopt(request->sockfd(), request->level(),
- request->optname(), buf.data(),
- &optlen));
- if (optlen >= 0) {
- response->mutable_optval()->set_bytesval(buf.data(), optlen);
- }
- break;
- }
- case ::posix_server::GetSockOptRequest::INT: {
- int intval = 0;
- socklen_t optlen = sizeof(intval);
- response->set_ret(::getsockopt(request->sockfd(), request->level(),
- request->optname(), &intval, &optlen));
- response->mutable_optval()->set_intval(intval);
- break;
- }
- case ::posix_server::GetSockOptRequest::TIME: {
- timeval tv;
- socklen_t optlen = sizeof(tv);
- response->set_ret(::getsockopt(request->sockfd(), request->level(),
- request->optname(), &tv, &optlen));
- response->mutable_optval()->mutable_timeval()->set_seconds(tv.tv_sec);
- response->mutable_optval()->mutable_timeval()->set_microseconds(
- tv.tv_usec);
- break;
- }
- default:
- return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
- "Unknown SockOpt Type");
- }
- if (response->ret() < 0) {
- response->set_errno_(errno);
- }
- return ::grpc::Status::OK;
- }
-
- ::grpc::Status Listen(grpc::ServerContext *context,
- const ::posix_server::ListenRequest *request,
- ::posix_server::ListenResponse *response) override {
- response->set_ret(listen(request->sockfd(), request->backlog()));
- if (response->ret() < 0) {
- response->set_errno_(errno);
- }
- return ::grpc::Status::OK;
- }
-
- ::grpc::Status Poll(::grpc::ServerContext *context,
- const ::posix_server::PollRequest *request,
- ::posix_server::PollResponse *response) override {
- std::vector<struct pollfd> pfds;
- pfds.reserve(request->pfds_size());
- for (const auto &pfd : request->pfds()) {
- pfds.push_back({
- .fd = pfd.fd(),
- .events = static_cast<short>(pfd.events()),
- });
- }
- int ret = ::poll(pfds.data(), pfds.size(), request->timeout_millis());
-
- response->set_ret(ret);
- if (ret < 0) {
- response->set_errno_(errno);
- } else {
- // Only pollfds that have non-empty revents are returned, the client can't
- // rely on indexes of the request array.
- for (const auto &pfd : pfds) {
- if (pfd.revents) {
- auto *proto_pfd = response->add_pfds();
- proto_pfd->set_fd(pfd.fd);
- proto_pfd->set_events(pfd.revents);
- }
- }
- if (int ready = response->pfds_size(); ret != ready) {
- return ::grpc::Status(
- ::grpc::StatusCode::INTERNAL,
- absl::StrFormat(
- "poll's return value(%d) doesn't match the number of "
- "file descriptors that are actually ready(%d)",
- ret, ready));
- }
- }
- 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()));
- if (response->ret() < 0) {
- response->set_errno_(errno);
- }
- return ::grpc::Status::OK;
- }
-
- ::grpc::Status SendTo(::grpc::ServerContext *context,
- const ::posix_server::SendToRequest *request,
- ::posix_server::SendToResponse *response) override {
- if (!request->has_dest_addr()) {
- return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
- "Missing address");
- }
- sockaddr_storage addr;
- socklen_t addr_len;
- auto err = proto_to_sockaddr(request->dest_addr(), &addr, &addr_len);
- if (!err.ok()) {
- return err;
- }
-
- response->set_ret(::sendto(request->sockfd(), request->buf().data(),
- request->buf().size(), request->flags(),
- reinterpret_cast<sockaddr *>(&addr), addr_len));
- if (response->ret() < 0) {
- response->set_errno_(errno);
- }
- return ::grpc::Status::OK;
- }
-
- ::grpc::Status SetNonblocking(
- grpc::ServerContext *context,
- const ::posix_server::SetNonblockingRequest *request,
- ::posix_server::SetNonblockingResponse *response) override {
- int flags = fcntl(request->fd(), F_GETFL);
- if (flags == -1) {
- response->set_ret(-1);
- response->set_errno_(errno);
- response->set_cmd("F_GETFL");
- return ::grpc::Status::OK;
- }
- if (request->nonblocking()) {
- flags |= O_NONBLOCK;
- } else {
- flags &= ~O_NONBLOCK;
- }
- int ret = fcntl(request->fd(), F_SETFL, flags);
- response->set_ret(ret);
- if (ret == -1) {
- response->set_errno_(errno);
- response->set_cmd("F_SETFL");
- }
- return ::grpc::Status::OK;
- }
-
- ::grpc::Status SetSockOpt(
- grpc::ServerContext *context,
- const ::posix_server::SetSockOptRequest *request,
- ::posix_server::SetSockOptResponse *response) override {
- switch (request->optval().val_case()) {
- case ::posix_server::SockOptVal::kBytesval:
- response->set_ret(setsockopt(request->sockfd(), request->level(),
- request->optname(),
- request->optval().bytesval().c_str(),
- request->optval().bytesval().size()));
- break;
- case ::posix_server::SockOptVal::kIntval: {
- int opt = request->optval().intval();
- response->set_ret(::setsockopt(request->sockfd(), request->level(),
- request->optname(), &opt, sizeof(opt)));
- break;
- }
- case ::posix_server::SockOptVal::kTimeval: {
- timeval tv = {.tv_sec = static_cast<time_t>(
- request->optval().timeval().seconds()),
- .tv_usec = static_cast<suseconds_t>(
- request->optval().timeval().microseconds())};
- response->set_ret(setsockopt(request->sockfd(), request->level(),
- request->optname(), &tv, sizeof(tv)));
- break;
- }
- default:
- return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
- "Unknown SockOpt Type");
- }
- if (response->ret() < 0) {
- response->set_errno_(errno);
- }
- return ::grpc::Status::OK;
- }
-
- ::grpc::Status Socket(grpc::ServerContext *context,
- const ::posix_server::SocketRequest *request,
- ::posix_server::SocketResponse *response) override {
- response->set_fd(
- socket(request->domain(), request->type(), request->protocol()));
- if (response->fd() < 0) {
- response->set_errno_(errno);
- }
- return ::grpc::Status::OK;
- }
-
- ::grpc::Status Shutdown(grpc::ServerContext *context,
- const ::posix_server::ShutdownRequest *request,
- ::posix_server::ShutdownResponse *response) override {
- response->set_ret(shutdown(request->fd(), request->how()));
- if (response->ret() < 0) {
- 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()));
- if (response->ret() >= 0) {
- response->set_buf(buf.data(), response->ret());
- }
- if (response->ret() < 0) {
- response->set_errno_(errno);
- }
- 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/netdevs/BUILD b/test/packetimpact/netdevs/BUILD
deleted file mode 100644
index 8d1193fed..000000000
--- a/test/packetimpact/netdevs/BUILD
+++ /dev/null
@@ -1,23 +0,0 @@
-load("//tools:defs.bzl", "go_library", "go_test")
-
-package(
- licenses = ["notice"],
-)
-
-go_library(
- name = "netdevs",
- srcs = ["netdevs.go"],
- visibility = ["//test/packetimpact:__subpackages__"],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/header",
- ],
-)
-
-go_test(
- name = "netdevs_test",
- size = "small",
- srcs = ["netdevs_test.go"],
- library = ":netdevs",
- deps = ["@com_github_google_go_cmp//cmp:go_default_library"],
-)
diff --git a/test/packetimpact/netdevs/netdevs.go b/test/packetimpact/netdevs/netdevs.go
deleted file mode 100644
index 25dcfbf60..000000000
--- a/test/packetimpact/netdevs/netdevs.go
+++ /dev/null
@@ -1,122 +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 netdevs contains utilities for working with network devices.
-package netdevs
-
-import (
- "fmt"
- "net"
- "regexp"
- "strconv"
- "strings"
-
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/header"
-)
-
-// A DeviceInfo represents a network device.
-type DeviceInfo struct {
- ID uint32
- MAC net.HardwareAddr
- IPv4Addr net.IP
- IPv4Net *net.IPNet
- IPv6Addr net.IP
- IPv6Net *net.IPNet
-}
-
-var (
- deviceLine = regexp.MustCompile(`^\s*(\d+): (\w+)`)
- linkLine = regexp.MustCompile(`^\s*link/\w+ ([0-9a-fA-F:]+)`)
- inetLine = regexp.MustCompile(`^\s*inet ([0-9./]+)`)
- inet6Line = regexp.MustCompile(`^\s*inet6 ([0-9a-fA-F:/]+)`)
-)
-
-// ParseDevicesWithRegex will parse the output with the given regexps to produce
-// a map from device name to device information. It is assumed that deviceLine
-// contains both a name and an ID.
-func ParseDevicesWithRegex(cmdOutput string, deviceLine, linkLine, inetLine, inet6Line *regexp.Regexp) (map[string]DeviceInfo, error) {
- var currentDevice string
- var currentInfo DeviceInfo
- deviceInfos := make(map[string]DeviceInfo)
- for _, line := range strings.Split(cmdOutput, "\n") {
- if m := deviceLine.FindStringSubmatch(line); m != nil {
- if currentDevice != "" {
- deviceInfos[currentDevice] = currentInfo
- }
- id, err := strconv.ParseUint(m[1], 10, 32)
- if err != nil {
- return nil, fmt.Errorf("parsing device ID %s: %w", m[1], err)
- }
- currentInfo = DeviceInfo{ID: uint32(id)}
- currentDevice = m[2]
- } else if m := linkLine.FindStringSubmatch(line); m != nil {
- mac, err := net.ParseMAC(m[1])
- if err != nil {
- return nil, err
- }
- currentInfo.MAC = mac
- } else if m := inetLine.FindStringSubmatch(line); m != nil {
- ipv4Addr, ipv4Net, err := net.ParseCIDR(m[1])
- if err != nil {
- return nil, err
- }
- currentInfo.IPv4Addr = ipv4Addr
- currentInfo.IPv4Net = ipv4Net
- } else if m := inet6Line.FindStringSubmatch(line); m != nil {
- ipv6Addr, ipv6Net, err := net.ParseCIDR(m[1])
- if err != nil {
- return nil, err
- }
- currentInfo.IPv6Addr = ipv6Addr
- currentInfo.IPv6Net = ipv6Net
- }
- }
- if currentDevice != "" {
- deviceInfos[currentDevice] = currentInfo
- }
- return deviceInfos, nil
-}
-
-// ParseDevices parses the output from `ip addr show` into a map from device
-// name to information about the device.
-//
-// Note: if multiple IPv6 addresses are assigned to a device, the last address
-// displayed by `ip addr show` will be used. This is fine for packetimpact
-// because we will always only have at most one IPv6 address assigned to each
-// device.
-func ParseDevices(cmdOutput string) (map[string]DeviceInfo, error) {
- return ParseDevicesWithRegex(cmdOutput, deviceLine, linkLine, inetLine, inet6Line)
-}
-
-// MACToIP converts the MAC address to an IPv6 link local address as described
-// in RFC 4291 page 20: https://tools.ietf.org/html/rfc4291#page-20
-func MACToIP(mac net.HardwareAddr) net.IP {
- addr := make([]byte, header.IPv6AddressSize)
- addr[0] = 0xfe
- addr[1] = 0x80
- header.EthernetAdddressToModifiedEUI64IntoBuf(tcpip.LinkAddress(mac), addr[8:])
- return net.IP(addr)
-}
-
-// FindDeviceByIP finds a DeviceInfo and device name from an IP address in the
-// output of ParseDevices.
-func FindDeviceByIP(ip net.IP, devices map[string]DeviceInfo) (string, DeviceInfo, error) {
- for dev, info := range devices {
- if info.IPv4Addr.Equal(ip) {
- return dev, info, nil
- }
- }
- return "", DeviceInfo{}, fmt.Errorf("can't find %s on any interface", ip)
-}
diff --git a/test/packetimpact/netdevs/netdevs_test.go b/test/packetimpact/netdevs/netdevs_test.go
deleted file mode 100644
index 379386980..000000000
--- a/test/packetimpact/netdevs/netdevs_test.go
+++ /dev/null
@@ -1,227 +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 netdevs
-
-import (
- "fmt"
- "net"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-)
-
-func mustParseMAC(s string) net.HardwareAddr {
- mac, err := net.ParseMAC(s)
- if err != nil {
- panic(fmt.Sprintf("failed to parse test MAC %q: %s", s, err))
- }
- return mac
-}
-
-func TestParseDevices(t *testing.T) {
- for _, v := range []struct {
- desc string
- cmdOutput string
- want map[string]DeviceInfo
- }{
- {
- desc: "v4 and v6",
- cmdOutput: `
-1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
- link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
- inet 127.0.0.1/8 scope host lo
- valid_lft forever preferred_lft forever
- inet6 ::1/128 scope host
- valid_lft forever preferred_lft forever
-2613: eth0@if2614: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
- link/ether 02:42:c0:a8:09:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
- inet 192.168.9.2/24 brd 192.168.9.255 scope global eth0
- valid_lft forever preferred_lft forever
- inet6 fe80::42:c0ff:fea8:902/64 scope link tentative
- valid_lft forever preferred_lft forever
-2615: eth2@if2616: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
- link/ether 02:42:df:f5:e1:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0
- inet 223.245.225.10/24 brd 223.245.225.255 scope global eth2
- valid_lft forever preferred_lft forever
- inet6 fe80::42:dfff:fef5:e10a/64 scope link tentative
- valid_lft forever preferred_lft forever
-2617: eth1@if2618: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
- link/ether 02:42:da:33:13:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0
- inet 218.51.19.10/24 brd 218.51.19.255 scope global eth1
- valid_lft forever preferred_lft forever
- inet6 fe80::42:daff:fe33:130a/64 scope link tentative
- valid_lft forever preferred_lft forever`,
- want: map[string]DeviceInfo{
- "lo": {
- ID: 1,
- MAC: mustParseMAC("00:00:00:00:00:00"),
- IPv4Addr: net.IPv4(127, 0, 0, 1),
- IPv4Net: &net.IPNet{
- IP: net.IPv4(127, 0, 0, 0),
- Mask: net.CIDRMask(8, 32),
- },
- IPv6Addr: net.ParseIP("::1"),
- IPv6Net: &net.IPNet{
- IP: net.ParseIP("::1"),
- Mask: net.CIDRMask(128, 128),
- },
- },
- "eth0": {
- ID: 2613,
- MAC: mustParseMAC("02:42:c0:a8:09:02"),
- IPv4Addr: net.IPv4(192, 168, 9, 2),
- IPv4Net: &net.IPNet{
- IP: net.IPv4(192, 168, 9, 0),
- Mask: net.CIDRMask(24, 32),
- },
- IPv6Addr: net.ParseIP("fe80::42:c0ff:fea8:902"),
- IPv6Net: &net.IPNet{
- IP: net.ParseIP("fe80::"),
- Mask: net.CIDRMask(64, 128),
- },
- },
- "eth1": {
- ID: 2617,
- MAC: mustParseMAC("02:42:da:33:13:0a"),
- IPv4Addr: net.IPv4(218, 51, 19, 10),
- IPv4Net: &net.IPNet{
- IP: net.IPv4(218, 51, 19, 0),
- Mask: net.CIDRMask(24, 32),
- },
- IPv6Addr: net.ParseIP("fe80::42:daff:fe33:130a"),
- IPv6Net: &net.IPNet{
- IP: net.ParseIP("fe80::"),
- Mask: net.CIDRMask(64, 128),
- },
- },
- "eth2": {
- ID: 2615,
- MAC: mustParseMAC("02:42:df:f5:e1:0a"),
- IPv4Addr: net.IPv4(223, 245, 225, 10),
- IPv4Net: &net.IPNet{
- IP: net.IPv4(223, 245, 225, 0),
- Mask: net.CIDRMask(24, 32),
- },
- IPv6Addr: net.ParseIP("fe80::42:dfff:fef5:e10a"),
- IPv6Net: &net.IPNet{
- IP: net.ParseIP("fe80::"),
- Mask: net.CIDRMask(64, 128),
- },
- },
- },
- },
- {
- desc: "v4 only",
- cmdOutput: `
-2613: eth0@if2614: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
- link/ether 02:42:c0:a8:09:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
- inet 192.168.9.2/24 brd 192.168.9.255 scope global eth0
- valid_lft forever preferred_lft forever`,
- want: map[string]DeviceInfo{
- "eth0": {
- ID: 2613,
- MAC: mustParseMAC("02:42:c0:a8:09:02"),
- IPv4Addr: net.IPv4(192, 168, 9, 2),
- IPv4Net: &net.IPNet{
- IP: net.IPv4(192, 168, 9, 0),
- Mask: net.CIDRMask(24, 32),
- },
- },
- },
- },
- {
- desc: "v6 only",
- cmdOutput: `
-2615: eth2@if2616: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
- link/ether 02:42:df:f5:e1:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0
- inet6 fe80::42:dfff:fef5:e10a/64 scope link tentative
- valid_lft forever preferred_lft forever`,
- want: map[string]DeviceInfo{
- "eth2": {
- ID: 2615,
- MAC: mustParseMAC("02:42:df:f5:e1:0a"),
- IPv6Addr: net.ParseIP("fe80::42:dfff:fef5:e10a"),
- IPv6Net: &net.IPNet{
- IP: net.ParseIP("fe80::"),
- Mask: net.CIDRMask(64, 128),
- },
- },
- },
- },
- } {
- t.Run(v.desc, func(t *testing.T) {
- got, err := ParseDevices(v.cmdOutput)
- if err != nil {
- t.Errorf("ParseDevices(\n%s\n) got unexpected error: %s", v.cmdOutput, err)
- }
- if diff := cmp.Diff(v.want, got); diff != "" {
- t.Errorf("ParseDevices(\n%s\n) got output diff (-want, +got):\n%s", v.cmdOutput, diff)
- }
- })
- }
-}
-
-func TestParseDevicesErrors(t *testing.T) {
- for _, v := range []struct {
- desc string
- cmdOutput string
- }{
- {
- desc: "invalid MAC addr",
- cmdOutput: `
-2617: eth1@if2618: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
- link/ether 02:42:da:33:13:0a:ffffffff brd ff:ff:ff:ff:ff:ff link-netnsid 0
- inet 218.51.19.10/24 brd 218.51.19.255 scope global eth1
- valid_lft forever preferred_lft forever
- inet6 fe80::42:daff:fe33:130a/64 scope link tentative
- valid_lft forever preferred_lft forever`,
- },
- {
- desc: "invalid v4 addr",
- cmdOutput: `
-2617: eth1@if2618: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
- link/ether 02:42:da:33:13:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0
- inet 1234.4321.424242.0/24 brd 218.51.19.255 scope global eth1
- valid_lft forever preferred_lft forever
- inet6 fe80::42:daff:fe33:130a/64 scope link tentative
- valid_lft forever preferred_lft forever`,
- },
- {
- desc: "invalid v6 addr",
- cmdOutput: `
-2617: eth1@if2618: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
- link/ether 02:42:da:33:13:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0
- inet 218.51.19.10/24 brd 218.51.19.255 scope global eth1
- valid_lft forever preferred_lft forever
- inet6 fe80:ffffffff::42:daff:fe33:130a/64 scope link tentative
- valid_lft forever preferred_lft forever`,
- },
- {
- desc: "invalid CIDR missing prefixlen",
- cmdOutput: `
-2617: eth1@if2618: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
- link/ether 02:42:da:33:13:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0
- inet 218.51.19.10 brd 218.51.19.255 scope global eth1
- valid_lft forever preferred_lft forever
- inet6 fe80::42:daff:fe33:130a scope link tentative
- valid_lft forever preferred_lft forever`,
- },
- } {
- t.Run(v.desc, func(t *testing.T) {
- if _, err := ParseDevices(v.cmdOutput); err == nil {
- t.Errorf("ParseDevices(\n%s\n) succeeded unexpectedly, want error", v.cmdOutput)
- }
- })
- }
-}
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 175a65336..000000000
--- a/test/packetimpact/proto/posix_server.proto
+++ /dev/null
@@ -1,268 +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;
-}
-
-message SockOptVal {
- oneof val {
- bytes bytesval = 1;
- int32 intval = 2;
- Timeval timeval = 3;
- }
-}
-
-// 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 ConnectRequest {
- int32 sockfd = 1;
- Sockaddr addr = 2;
-}
-
-message ConnectResponse {
- 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 GetSockOptRequest {
- int32 sockfd = 1;
- int32 level = 2;
- int32 optname = 3;
- int32 optlen = 4;
- enum SockOptType {
- UNSPECIFIED = 0;
- BYTES = 1;
- INT = 2;
- TIME = 3;
- }
- SockOptType type = 5;
-}
-
-message GetSockOptResponse {
- int32 ret = 1;
- int32 errno_ = 2; // "errno" may fail to compile in c++.
- SockOptVal optval = 3;
-}
-
-message ListenRequest {
- int32 sockfd = 1;
- int32 backlog = 2;
-}
-
-message ListenResponse {
- int32 ret = 1;
- int32 errno_ = 2; // "errno" may fail to compile in c++.
-}
-
-// The events field is overloaded: when used for request, it is copied into the
-// events field of posix struct pollfd; when used for response, it is filled by
-// the revents field from the posix struct pollfd.
-message PollFd {
- int32 fd = 1;
- uint32 events = 2;
-}
-
-message PollRequest {
- repeated PollFd pfds = 1;
- int32 timeout_millis = 2;
-}
-
-message PollResponse {
- int32 ret = 1;
- int32 errno_ = 2; // "errno" may fail to compile in c++.
- repeated PollFd pfds = 3;
-}
-
-message SendRequest {
- int32 sockfd = 1;
- bytes buf = 2;
- int32 flags = 3;
-}
-
-message SendResponse {
- int32 ret = 1;
- int32 errno_ = 2; // "errno" may fail to compile in c++.
-}
-
-message SendToRequest {
- int32 sockfd = 1;
- bytes buf = 2;
- int32 flags = 3;
- Sockaddr dest_addr = 4;
-}
-
-message SendToResponse {
- int32 ret = 1;
- int32 errno_ = 2; // "errno" may fail to compile in c++.
-}
-
-message SetNonblockingRequest {
- int32 fd = 1;
- bool nonblocking = 2;
-}
-
-message SetNonblockingResponse {
- int32 ret = 1;
- int32 errno_ = 2; // "errno" may fail to compile in c++.
- // The failed fcntl cmd.
- string cmd = 3;
-}
-
-message SetSockOptRequest {
- int32 sockfd = 1;
- int32 level = 2;
- int32 optname = 3;
- SockOptVal optval = 4;
-}
-
-message SetSockOptResponse {
- 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 ShutdownRequest {
- int32 fd = 1;
- int32 how = 2;
-}
-
-message ShutdownResponse {
- int32 ret = 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 connect() on the DUT.
- rpc Connect(ConnectRequest) returns (ConnectResponse);
- // Call getsockname() on the DUT.
- rpc GetSockName(GetSockNameRequest) returns (GetSockNameResponse);
- // Call getsockopt() on the DUT.
- rpc GetSockOpt(GetSockOptRequest) returns (GetSockOptResponse);
- // Call listen() on the DUT.
- rpc Listen(ListenRequest) returns (ListenResponse);
- // Call poll() on the DUT. Only pollfds that have non-empty revents are
- // returned, the only way to tie the response back to the original request
- // is using the fd number.
- rpc Poll(PollRequest) returns (PollResponse);
- // Call send() on the DUT.
- rpc Send(SendRequest) returns (SendResponse);
- // Call sendto() on the DUT.
- rpc SendTo(SendToRequest) returns (SendToResponse);
- // Set/Clear O_NONBLOCK flag on the requested fd. This is needed because the
- // operating system on DUT may have a different definition for O_NONBLOCK, it
- // is not sound to assemble flags on testbench.
- rpc SetNonblocking(SetNonblockingRequest) returns (SetNonblockingResponse);
- // Call setsockopt() on the DUT.
- rpc SetSockOpt(SetSockOptRequest) returns (SetSockOptResponse);
- // Call socket() on the DUT.
- rpc Socket(SocketRequest) returns (SocketResponse);
- // Call shutdown() on the DUT.
- rpc Shutdown(ShutdownRequest) returns (ShutdownResponse);
- // Call recv() on the DUT.
- rpc Recv(RecvRequest) returns (RecvResponse);
-}
diff --git a/test/packetimpact/runner/BUILD b/test/packetimpact/runner/BUILD
deleted file mode 100644
index 888c44343..000000000
--- a/test/packetimpact/runner/BUILD
+++ /dev/null
@@ -1,38 +0,0 @@
-load("//tools:defs.bzl", "bzl_library", "go_library", "go_test")
-
-package(
- default_visibility = ["//test/packetimpact:__subpackages__"],
- licenses = ["notice"],
-)
-
-go_test(
- name = "packetimpact_test",
- srcs = [
- "packetimpact_test.go",
- ],
- tags = [
- # Not intended to be run directly.
- "local",
- "manual",
- ],
- deps = [":runner"],
-)
-
-bzl_library(
- name = "defs_bzl",
- srcs = ["defs.bzl"],
- visibility = ["//test/packetimpact:__subpackages__"],
-)
-
-go_library(
- name = "runner",
- testonly = True,
- srcs = ["dut.go"],
- visibility = ["//test/packetimpact:__subpackages__"],
- deps = [
- "//pkg/test/dockerutil",
- "//test/packetimpact/netdevs",
- "//test/packetimpact/testbench",
- "@com_github_docker_docker//api/types/mount:go_default_library",
- ],
-)
diff --git a/test/packetimpact/runner/defs.bzl b/test/packetimpact/runner/defs.bzl
deleted file mode 100644
index 19f6cc0e0..000000000
--- a/test/packetimpact/runner/defs.bzl
+++ /dev/null
@@ -1,315 +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 -or -type d -exec chmod a+rx {} \\;",
- "%s %s --testbench_binary %s --num_duts %d $@\n" % (
- test_runner.short_path,
- " ".join(ctx.attr.flags),
- ctx.files.testbench_binary[0].short_path,
- ctx.attr.num_duts,
- ),
- ])
- ctx.actions.write(bench, bench_content, is_executable = True)
-
- transitive_files = []
- if hasattr(ctx.attr._test_runner, "data_runfiles"):
- transitive_files.append(ctx.attr._test_runner.data_runfiles.files)
- files = [test_runner] + ctx.files.testbench_binary + ctx.files._posix_server
- runfiles = ctx.runfiles(
- files = files,
- transitive_files = depset(transitive = 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 = ":packetimpact_test",
- ),
- "_posix_server": 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 = [],
- ),
- "num_duts": attr.int(
- mandatory = False,
- default = 1,
- ),
- },
- test = True,
- implementation = _packetimpact_test_impl,
-)
-
-PACKETIMPACT_TAGS = [
- "local",
- "manual",
- "packetimpact",
-]
-
-def packetimpact_native_test(
- name,
- testbench_binary,
- expect_failure = False,
- **kwargs):
- """Add a native packetimpact test.
-
- 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 = ["--expect_failure"] if expect_failure else []
- _packetimpact_test(
- name = name + "_native_test",
- testbench_binary = testbench_binary,
- flags = ["--native"] + expect_failure_flag,
- tags = PACKETIMPACT_TAGS,
- **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,
- # Note that a distinct runtime must be provided in the form
- # --test_arg=--runtime=other when invoking bazel.
- flags = expect_failure_flag,
- tags = PACKETIMPACT_TAGS,
- **kwargs
- )
-
-def packetimpact_go_test(name, expect_native_failure = False, expect_netstack_failure = False, num_duts = 1, **kwargs):
- """Add packetimpact tests written in go.
-
- Args:
- name: name of the test
- expect_native_failure: the test must fail natively
- expect_netstack_failure: the test must fail for Netstack
- num_duts: how many DUTs are needed for the test
- **kwargs: all the other args, forwarded to packetimpact_native_test and packetimpact_netstack_test
- """
- testbench_binary = name + "_test"
- packetimpact_native_test(
- name = name,
- expect_failure = expect_native_failure,
- num_duts = num_duts,
- testbench_binary = testbench_binary,
- **kwargs
- )
- packetimpact_netstack_test(
- name = name,
- expect_failure = expect_netstack_failure,
- num_duts = num_duts,
- testbench_binary = testbench_binary,
- **kwargs
- )
-
-def packetimpact_testbench(name, size = "small", pure = True, **kwargs):
- """Build packetimpact testbench written in go.
-
- Args:
- name: name of the test
- size: size of the test
- pure: make a static go binary
- **kwargs: all the other args, forwarded to go_test
- """
- go_test(
- name = name + "_test",
- size = size,
- pure = pure,
- nogo = False, # FIXME(gvisor.dev/issue/3374): Not working with all build systems.
- tags = [
- "local",
- "manual",
- ],
- **kwargs
- )
-
-PacketimpactTestInfo = provider(
- doc = "Provide information for packetimpact tests",
- fields = [
- "name",
- "timeout",
- "expect_netstack_failure",
- "num_duts",
- ],
-)
-
-ALL_TESTS = [
- PacketimpactTestInfo(
- name = "fin_wait2_timeout",
- ),
- PacketimpactTestInfo(
- name = "ipv4_id_uniqueness",
- ),
- PacketimpactTestInfo(
- name = "udp_discard_mcast_source_addr",
- ),
- PacketimpactTestInfo(
- name = "udp_any_addr_recv_unicast",
- ),
- PacketimpactTestInfo(
- name = "udp_icmp_error_propagation",
- ),
- PacketimpactTestInfo(
- name = "tcp_window_shrink",
- ),
- PacketimpactTestInfo(
- name = "tcp_zero_window_probe",
- ),
- PacketimpactTestInfo(
- name = "tcp_zero_window_probe_retransmit",
- ),
- PacketimpactTestInfo(
- name = "tcp_zero_window_probe_usertimeout",
- ),
- PacketimpactTestInfo(
- name = "tcp_retransmits",
- ),
- PacketimpactTestInfo(
- name = "tcp_outside_the_window",
- ),
- PacketimpactTestInfo(
- name = "tcp_noaccept_close_rst",
- ),
- PacketimpactTestInfo(
- name = "tcp_send_window_sizes_piggyback",
- ),
- PacketimpactTestInfo(
- name = "tcp_unacc_seq_ack",
- ),
- PacketimpactTestInfo(
- name = "tcp_paws_mechanism",
- # TODO(b/156682000): Fix netstack then remove the line below.
- expect_netstack_failure = True,
- ),
- PacketimpactTestInfo(
- name = "tcp_user_timeout",
- ),
- PacketimpactTestInfo(
- name = "tcp_zero_receive_window",
- ),
- PacketimpactTestInfo(
- name = "tcp_queue_send_recv_in_syn_sent",
- ),
- PacketimpactTestInfo(
- name = "tcp_synsent_reset",
- ),
- PacketimpactTestInfo(
- name = "tcp_synrcvd_reset",
- ),
- PacketimpactTestInfo(
- name = "tcp_network_unreachable",
- ),
- PacketimpactTestInfo(
- name = "tcp_cork_mss",
- ),
- PacketimpactTestInfo(
- name = "tcp_handshake_window_size",
- ),
- PacketimpactTestInfo(
- name = "tcp_timewait_reset",
- # TODO(b/168523247): Fix netstack then remove the line below.
- expect_netstack_failure = True,
- ),
- PacketimpactTestInfo(
- name = "tcp_listen_backlog",
- ),
- PacketimpactTestInfo(
- name = "tcp_syncookie",
- ),
- PacketimpactTestInfo(
- name = "tcp_connect_icmp_error",
- ),
- PacketimpactTestInfo(
- name = "icmpv6_param_problem",
- ),
- PacketimpactTestInfo(
- name = "ipv6_unknown_options_action",
- ),
- PacketimpactTestInfo(
- name = "ipv4_fragment_reassembly",
- ),
- PacketimpactTestInfo(
- name = "ipv6_fragment_reassembly",
- ),
- PacketimpactTestInfo(
- name = "ipv6_fragment_icmp_error",
- num_duts = 3,
- ),
- PacketimpactTestInfo(
- name = "tcp_linger",
- ),
- PacketimpactTestInfo(
- name = "tcp_rcv_buf_space",
- ),
- PacketimpactTestInfo(
- name = "tcp_rack",
- expect_netstack_failure = True,
- ),
- PacketimpactTestInfo(
- name = "tcp_info",
- ),
- PacketimpactTestInfo(
- name = "tcp_fin_retransmission",
- ),
- PacketimpactTestInfo(
- name = "generic_dgram_socket_send_recv",
- timeout = "long",
- ),
-]
-
-def validate_all_tests():
- """
- Make sure that ALL_TESTS list is in sync with the rules in BUILD.
-
- This function is order-dependent, it is intended to be used after
- all packetimpact_testbench rules and before using ALL_TESTS list
- at the end of BUILD.
- """
- all_tests_dict = {} # there is no set, using dict to approximate.
- for test in ALL_TESTS:
- rule_name = test.name + "_test"
- all_tests_dict[rule_name] = True
- if not native.existing_rule(rule_name):
- fail("%s does not have a packetimpact_testbench rule in BUILD" % test.name)
- for name in native.existing_rules():
- if name.endswith("_test") and name not in all_tests_dict:
- fail("%s is not declared in ALL_TESTS list in defs.bzl" % name[:-5])
diff --git a/test/packetimpact/runner/dut.go b/test/packetimpact/runner/dut.go
deleted file mode 100644
index 02678a76a..000000000
--- a/test/packetimpact/runner/dut.go
+++ /dev/null
@@ -1,657 +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 runner starts docker containers and networking for a packetimpact test.
-package runner
-
-import (
- "context"
- "encoding/json"
- "flag"
- "fmt"
- "io/ioutil"
- "log"
- "math/rand"
- "net"
- "os"
- "os/exec"
- "path"
- "path/filepath"
- "strings"
- "testing"
- "time"
-
- "github.com/docker/docker/api/types/mount"
- "gvisor.dev/gvisor/pkg/test/dockerutil"
- "gvisor.dev/gvisor/test/packetimpact/netdevs"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-// stringList implements flag.Value.
-type stringList []string
-
-// String implements flag.Value.String.
-func (l *stringList) String() string {
- return strings.Join(*l, ",")
-}
-
-// Set implements flag.Value.Set.
-func (l *stringList) Set(value string) error {
- *l = append(*l, value)
- return nil
-}
-
-var (
- native = false
- testbenchBinary = ""
- tshark = false
- extraTestArgs = stringList{}
- expectFailure = false
- numDUTs = 1
-
- // DUTAddr is the IP addres for DUT.
- DUTAddr = net.IPv4(0, 0, 0, 10)
- testbenchAddr = net.IPv4(0, 0, 0, 20)
-)
-
-// RegisterFlags defines flags and associates them with the package-level
-// exported variables above. It should be called by tests in their init
-// functions.
-func RegisterFlags(fs *flag.FlagSet) {
- fs.BoolVar(&native, "native", false, "whether the test should be run natively")
- fs.StringVar(&testbenchBinary, "testbench_binary", "", "path to the testbench binary")
- fs.BoolVar(&tshark, "tshark", false, "use more verbose tshark in logs instead of tcpdump")
- fs.Var(&extraTestArgs, "extra_test_arg", "extra arguments to pass to the testbench")
- fs.BoolVar(&expectFailure, "expect_failure", false, "expect that the test will fail when run")
- fs.IntVar(&numDUTs, "num_duts", numDUTs, "the number of duts to create")
-}
-
-const (
- // CtrlPort is the port that posix_server listens on.
- CtrlPort uint16 = 40000
- // testOutputDir is the directory in each container that holds test output.
- testOutputDir = "/tmp/testoutput"
-)
-
-// logger implements testutil.Logger.
-//
-// Labels logs based on their source and formats multi-line logs.
-type logger string
-
-// Name implements testutil.Logger.Name.
-func (l logger) Name() string {
- return string(l)
-}
-
-// Logf implements testutil.Logger.Logf.
-func (l logger) Logf(format string, args ...interface{}) {
- lines := strings.Split(fmt.Sprintf(format, args...), "\n")
- log.Printf("%s: %s", l, lines[0])
- for _, line := range lines[1:] {
- log.Printf("%*s %s", len(l), "", line)
- }
-}
-
-// dutInfo encapsulates all the essential information to set up testbench
-// container.
-type dutInfo struct {
- dut DUT
- ctrlNet, testNet *dockerutil.Network
- netInfo *testbench.DUTTestNet
- uname *testbench.DUTUname
-}
-
-// setUpDUT will set up one DUT and return information for setting up the
-// container for testbench.
-func setUpDUT(ctx context.Context, t *testing.T, id int, mkDevice func(*dockerutil.Container) DUT) (dutInfo, error) {
- // Create the networks needed for the test. One control network is needed
- // for the gRPC control packets and one test network on which to transmit
- // the test packets.
- var info dutInfo
- ctrlNet := dockerutil.NewNetwork(ctx, logger("ctrlNet"))
- testNet := dockerutil.NewNetwork(ctx, logger("testNet"))
- for _, dn := range []*dockerutil.Network{ctrlNet, testNet} {
- for {
- if err := createDockerNetwork(ctx, dn); err != nil {
- t.Log("creating docker network:", err)
- const wait = 100 * time.Millisecond
- t.Logf("sleeping %s and will try creating docker network again", wait)
- // This can fail if another docker network claimed the same IP so we
- // will just try again.
- time.Sleep(wait)
- continue
- }
- break
- }
- dn := dn
- t.Cleanup(func() {
- if err := dn.Cleanup(ctx); err != nil {
- t.Errorf("failed to cleanup network %s: %s", dn.Name, err)
- }
- })
- // Sanity check.
- if inspect, err := dn.Inspect(ctx); err != nil {
- return dutInfo{}, fmt.Errorf("failed to inspect network %s: %w", dn.Name, err)
- } else if inspect.Name != dn.Name {
- return dutInfo{}, fmt.Errorf("name mismatch for network want: %s got: %s", dn.Name, inspect.Name)
- }
- }
- info.ctrlNet = ctrlNet
- info.testNet = testNet
-
- // Create the Docker container for the DUT.
- makeContainer := dockerutil.MakeContainer
- if native {
- makeContainer = dockerutil.MakeNativeContainer
- }
- dutContainer := makeContainer(ctx, logger(fmt.Sprintf("dut-%d", id)))
- t.Cleanup(func() {
- dutContainer.CleanUp(ctx)
- })
- info.dut = mkDevice(dutContainer)
-
- runOpts := dockerutil.RunOpts{
- Image: "packetimpact",
- CapAdd: []string{"NET_ADMIN"},
- }
- if _, err := MountTempDirectory(t, &runOpts, "dut-output", testOutputDir); err != nil {
- return dutInfo{}, err
- }
-
- ipv4PrefixLength, _ := testNet.Subnet.Mask.Size()
- remoteIPv6, remoteMAC, dutDeviceID, dutTestNetDev, err := info.dut.Prepare(ctx, t, runOpts, ctrlNet, testNet)
- if err != nil {
- return dutInfo{}, err
- }
- info.netInfo = &testbench.DUTTestNet{
- RemoteMAC: remoteMAC,
- RemoteIPv4: AddressInSubnet(DUTAddr, *testNet.Subnet),
- RemoteIPv6: remoteIPv6,
- RemoteDevID: dutDeviceID,
- RemoteDevName: dutTestNetDev,
- LocalIPv4: AddressInSubnet(testbenchAddr, *testNet.Subnet),
- IPv4PrefixLength: ipv4PrefixLength,
- POSIXServerIP: AddressInSubnet(DUTAddr, *ctrlNet.Subnet),
- POSIXServerPort: CtrlPort,
- }
- info.uname, err = info.dut.Uname(ctx)
- if err != nil {
- return dutInfo{}, fmt.Errorf("failed to get uname information on DUT: %w", err)
- }
- return info, nil
-}
-
-// TestWithDUT runs a packetimpact test with the given information.
-func TestWithDUT(ctx context.Context, t *testing.T, mkDevice func(*dockerutil.Container) DUT) {
- if testbenchBinary == "" {
- t.Fatal("--testbench_binary is missing")
- }
- dockerutil.EnsureSupportedDockerVersion()
-
- dutInfoChan := make(chan dutInfo, numDUTs)
- errChan := make(chan error, numDUTs)
- var dockerNetworks []*dockerutil.Network
- var dutInfos []*testbench.DUTInfo
- var duts []DUT
-
- setUpCtx, cancelSetup := context.WithCancel(ctx)
- t.Cleanup(cancelSetup)
- for i := 0; i < numDUTs; i++ {
- go func(i int) {
- info, err := setUpDUT(setUpCtx, t, i, mkDevice)
- if err != nil {
- errChan <- err
- } else {
- dutInfoChan <- info
- }
- }(i)
- }
- for i := 0; i < numDUTs; i++ {
- select {
- case info := <-dutInfoChan:
- dockerNetworks = append(dockerNetworks, info.ctrlNet, info.testNet)
- dutInfos = append(dutInfos, &testbench.DUTInfo{
- Net: info.netInfo,
- Uname: info.uname,
- })
- duts = append(duts, info.dut)
- case err := <-errChan:
- t.Fatal(err)
- }
- }
-
- // Create the Docker container for the testbench.
- testbenchContainer := dockerutil.MakeNativeContainer(ctx, logger("testbench"))
- t.Cleanup(func() {
- testbenchContainer.CleanUp(ctx)
- })
-
- runOpts := dockerutil.RunOpts{
- Image: "packetimpact",
- CapAdd: []string{"NET_ADMIN"},
- }
- if _, err := MountTempDirectory(t, &runOpts, "testbench-output", testOutputDir); err != nil {
- t.Fatal(err)
- }
- tbb := path.Base(testbenchBinary)
- containerTestbenchBinary := filepath.Join("/packetimpact", tbb)
- testbenchContainer.CopyFiles(&runOpts, "/packetimpact", filepath.Join("test/packetimpact/tests", tbb))
-
- if err := StartContainer(
- ctx,
- runOpts,
- testbenchContainer,
- testbenchAddr,
- dockerNetworks,
- nil, /* sysctls */
- "tail", "-f", "/dev/null",
- ); err != nil {
- t.Fatalf("cannot start testbench container: %s", err)
- }
-
- for i := range dutInfos {
- name, info, err := deviceByIP(ctx, testbenchContainer, dutInfos[i].Net.LocalIPv4)
- if err != nil {
- t.Fatalf("failed to get the device name associated with %s: %s", dutInfos[i].Net.LocalIPv4, err)
- }
- dutInfos[i].Net.LocalDevName = name
- dutInfos[i].Net.LocalDevID = info.ID
- dutInfos[i].Net.LocalMAC = info.MAC
- localIPv6, err := getOrAssignIPv6Addr(ctx, testbenchContainer, name)
- if err != nil {
- t.Fatalf("failed to get IPV6 address on %s: %s", testbenchContainer.Name, err)
- }
- dutInfos[i].Net.LocalIPv6 = localIPv6
- }
- dutInfosBytes, err := json.Marshal(dutInfos)
- if err != nil {
- t.Fatalf("failed to marshal %v into json: %s", dutInfos, err)
- }
-
- baseSnifferArgs := []string{
- "tcpdump",
- "-vvv",
- "--absolute-tcp-sequence-numbers",
- "--packet-buffered",
- // Disable DNS resolution.
- "-n",
- // run tcpdump as root since the output directory is owned by root. From
- // `man tcpdump`:
- //
- // -Z user
- // --relinquish-privileges=user
- // If tcpdump is running as root, after opening the capture device
- // or input savefile, change the user ID to user and the group ID to
- // the primary group of user.
- // This behavior is enabled by default (-Z tcpdump), and can be
- // disabled by -Z root.
- "-Z", "root",
- }
- if tshark {
- baseSnifferArgs = []string{
- "tshark",
- "-V",
- "-o", "tcp.check_checksum:TRUE",
- "-o", "udp.check_checksum:TRUE",
- // Disable buffering.
- "-l",
- // Disable DNS resolution.
- "-n",
- }
- }
- for _, info := range dutInfos {
- n := info.Net
- snifferArgs := append(baseSnifferArgs, "-i", n.LocalDevName)
- if !tshark {
- snifferArgs = append(
- snifferArgs,
- "-w",
- filepath.Join(testOutputDir, fmt.Sprintf("%s.pcap", n.LocalDevName)),
- )
- }
- p, err := testbenchContainer.ExecProcess(ctx, dockerutil.ExecOpts{}, snifferArgs...)
- if err != nil {
- t.Fatalf("failed to start exec a sniffer on %s: %s", n.LocalDevName, err)
- }
- t.Cleanup(func() {
- if snifferOut, err := p.Logs(); err != nil {
- t.Errorf("sniffer logs failed: %s\n%s", err, snifferOut)
- } else {
- t.Logf("sniffer logs:\n%s", snifferOut)
- }
- })
- // When the Linux kernel receives a SYN-ACK for a SYN it didn't send, it
- // will respond with an RST. In most packetimpact tests, the SYN is sent
- // by the raw socket, the kernel knows nothing about the connection, this
- // behavior will break lots of TCP related packetimpact tests. To prevent
- // this, we can install the following iptables rules. The raw socket that
- // packetimpact tests use will still be able to see everything.
- for _, bin := range []string{"iptables", "ip6tables"} {
- if logs, err := testbenchContainer.Exec(ctx, dockerutil.ExecOpts{}, bin, "-A", "INPUT", "-i", n.LocalDevName, "-p", "tcp", "-j", "DROP"); err != nil {
- t.Fatalf("unable to Exec %s on container %s: %s, logs from testbench:\n%s", bin, testbenchContainer.Name, err, logs)
- }
- }
- }
-
- t.Cleanup(func() {
- // Wait 1 second before killing tcpdump to give it time to flush
- // any packets. On linux tests killing it immediately can
- // sometimes result in partial pcaps.
- time.Sleep(1 * time.Second)
- if logs, err := testbenchContainer.Exec(ctx, dockerutil.ExecOpts{}, "killall", baseSnifferArgs[0]); err != nil {
- t.Errorf("failed to kill all sniffers: %s, logs: %s", err, logs)
- }
- })
-
- // FIXME(b/156449515): Some piece of the system has a race. The old
- // bash script version had a sleep, so we have one too. The race should
- // be fixed and this sleep removed.
- time.Sleep(time.Second)
-
- // 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.
- testArgs := []string{containerTestbenchBinary}
- if testing.Verbose() {
- testArgs = append(testArgs, "-test.v")
- }
- testArgs = append(testArgs, extraTestArgs...)
- testArgs = append(testArgs,
- fmt.Sprintf("--native=%t", native),
- "--dut_infos_json", string(dutInfosBytes),
- )
- testbenchLogs, err := testbenchContainer.Exec(ctx, dockerutil.ExecOpts{}, testArgs...)
- var dutLogs string
- for i, dut := range duts {
- logs, err := dut.Logs(ctx)
- if err != nil {
- logs = fmt.Sprintf("failed to fetch DUT logs: %s", err)
- }
- dutLogs = fmt.Sprintf(`%s====== Begin of DUT-%d Logs ======
-
-%s
-
-====== End of DUT-%d Logs ======
-
-`, dutLogs, i, logs, i)
- }
- testLogs := fmt.Sprintf(`
-%s====== Begin of Testbench Logs ======
-
-%s
-
-====== End of Testbench Logs ======`, dutLogs, testbenchLogs)
- if (err != nil) != expectFailure {
- t.Errorf(`test error: %v, expect failure: %t
-%s`, err, expectFailure, testLogs)
- } else if expectFailure {
- t.Logf(`test failed as expected: %v
-%s`, err, testLogs)
- } else if testing.Verbose() {
- t.Log(testLogs)
- }
-}
-
-// DUT describes how to setup/teardown the dut for packetimpact tests.
-type DUT interface {
- // Prepare prepares the dut, starts posix_server and returns the IPv6, MAC
- // address, the interface ID, and the interface name for the testNet on DUT.
- // The t parameter is supposed to be used for t.Cleanup. Don't use it for
- // t.Fatal/FailNow functions.
- Prepare(ctx context.Context, t *testing.T, runOpts dockerutil.RunOpts, ctrlNet, testNet *dockerutil.Network) (net.IP, net.HardwareAddr, uint32, string, error)
-
- // Uname gathers information of DUT using command uname.
- Uname(ctx context.Context) (*testbench.DUTUname, error)
-
- // Logs retrieves the logs from the dut.
- Logs(ctx context.Context) (string, error)
-}
-
-// DockerDUT describes a docker based DUT.
-type DockerDUT struct {
- c *dockerutil.Container
-}
-
-// NewDockerDUT creates a docker based DUT.
-func NewDockerDUT(c *dockerutil.Container) DUT {
- return &DockerDUT{
- c: c,
- }
-}
-
-// Prepare implements DUT.Prepare.
-func (dut *DockerDUT) Prepare(ctx context.Context, _ *testing.T, runOpts dockerutil.RunOpts, ctrlNet, testNet *dockerutil.Network) (net.IP, net.HardwareAddr, uint32, string, error) {
- const containerPosixServerBinary = "/packetimpact/posix_server"
- dut.c.CopyFiles(&runOpts, "/packetimpact", "test/packetimpact/dut/posix_server")
-
- if err := StartContainer(
- ctx,
- runOpts,
- dut.c,
- DUTAddr,
- []*dockerutil.Network{ctrlNet, testNet},
- map[string]string{
- // This enables creating ICMP sockets on Linux.
- "net.ipv4.ping_group_range": "0 0",
- },
- containerPosixServerBinary,
- "--ip=0.0.0.0",
- fmt.Sprintf("--port=%d", CtrlPort),
- ); err != nil {
- return nil, nil, 0, "", fmt.Errorf("failed to start docker container for DUT: %w", err)
- }
-
- if _, err := dut.c.WaitForOutput(ctx, "Server listening.*\n", 60*time.Second); err != nil {
- return nil, nil, 0, "", fmt.Errorf("%s on container %s never listened: %s", containerPosixServerBinary, dut.c.Name, err)
- }
-
- dutTestDevice, dutDeviceInfo, err := deviceByIP(ctx, dut.c, AddressInSubnet(DUTAddr, *testNet.Subnet))
- if err != nil {
- return nil, nil, 0, "", err
- }
-
- remoteIPv6, err := getOrAssignIPv6Addr(ctx, dut.c, dutTestDevice)
- if err != nil {
- return nil, nil, 0, "", fmt.Errorf("failed to get IPv6 address on %s: %s", dut.c.Name, err)
- }
- const testNetDev = "eth2"
-
- return remoteIPv6, dutDeviceInfo.MAC, dutDeviceInfo.ID, testNetDev, nil
-}
-
-// Uname implements DUT.Uname.
-func (dut *DockerDUT) Uname(ctx context.Context) (*testbench.DUTUname, error) {
- machine, err := dut.c.Exec(ctx, dockerutil.ExecOpts{}, "uname", "-m")
- if err != nil {
- return nil, err
- }
- kernelRelease, err := dut.c.Exec(ctx, dockerutil.ExecOpts{}, "uname", "-r")
- if err != nil {
- return nil, err
- }
- kernelVersion, err := dut.c.Exec(ctx, dockerutil.ExecOpts{}, "uname", "-v")
- if err != nil {
- return nil, err
- }
- kernelName, err := dut.c.Exec(ctx, dockerutil.ExecOpts{}, "uname", "-s")
- if err != nil {
- return nil, err
- }
- // TODO(gvisor.dev/issues/5586): -o is not supported on macOS.
- operatingSystem, err := dut.c.Exec(ctx, dockerutil.ExecOpts{}, "uname", "-o")
- if err != nil {
- return nil, err
- }
- return &testbench.DUTUname{
- Machine: strings.TrimRight(machine, "\n"),
- KernelName: strings.TrimRight(kernelName, "\n"),
- KernelRelease: strings.TrimRight(kernelRelease, "\n"),
- KernelVersion: strings.TrimRight(kernelVersion, "\n"),
- OperatingSystem: strings.TrimRight(operatingSystem, "\n"),
- }, nil
-}
-
-// Logs implements DUT.Logs.
-func (dut *DockerDUT) Logs(ctx context.Context) (string, error) {
- logs, err := dut.c.Logs(ctx)
- if err != nil {
- return "", err
- }
- return logs, nil
-}
-
-// AddNetworks connects docker network with the container and assigns the specific IP.
-func AddNetworks(ctx context.Context, d *dockerutil.Container, addr net.IP, networks []*dockerutil.Network) error {
- for _, dn := range networks {
- ip := AddressInSubnet(addr, *dn.Subnet)
- // Connect to the network with the specified IP address.
- if err := dn.Connect(ctx, d, ip.String(), ""); err != nil {
- return fmt.Errorf("unable to connect container %s to network %s: %w", d.Name, dn.Name, err)
- }
- }
- return nil
-}
-
-// AddressInSubnet combines the subnet provided with the address and returns a
-// new address. The return address bits come from the subnet where the mask is
-// 1 and from the ip address where the mask is 0.
-func AddressInSubnet(addr net.IP, subnet net.IPNet) net.IP {
- var octets net.IP
- for i := 0; i < 4; i++ {
- octets = append(octets, (subnet.IP.To4()[i]&subnet.Mask[i])+(addr.To4()[i]&(^subnet.Mask[i])))
- }
- return octets
-}
-
-// devicesInfo will run "ip addr show" on the container and parse the output
-// to a map[string]netdevs.DeviceInfo.
-func devicesInfo(ctx context.Context, d *dockerutil.Container) (map[string]netdevs.DeviceInfo, error) {
- out, err := d.Exec(ctx, dockerutil.ExecOpts{}, "ip", "addr", "show")
- if err != nil {
- return map[string]netdevs.DeviceInfo{}, fmt.Errorf("listing devices on %s container: %w\n%s", d.Name, err, out)
- }
- devs, err := netdevs.ParseDevices(out)
- if err != nil {
- return map[string]netdevs.DeviceInfo{}, fmt.Errorf("parsing devices from %s container: %w\n%s", d.Name, err, out)
- }
- return devs, nil
-}
-
-// deviceByIP finds a deviceInfo and device name from an IP address.
-func deviceByIP(ctx context.Context, d *dockerutil.Container, ip net.IP) (string, netdevs.DeviceInfo, error) {
- devs, err := devicesInfo(ctx, d)
- if err != nil {
- return "", netdevs.DeviceInfo{}, err
- }
- testDevice, deviceInfo, err := netdevs.FindDeviceByIP(ip, devs)
- if err != nil {
- return "", netdevs.DeviceInfo{}, fmt.Errorf("can't find deviceInfo for container %s: %w", d.Name, err)
- }
- return testDevice, deviceInfo, nil
-}
-
-// getOrAssignIPv6Addr will try to get the IPv6 address for the interface; if an
-// address was not assigned, a link-local address based on MAC will be assigned
-// to that interface.
-func getOrAssignIPv6Addr(ctx context.Context, d *dockerutil.Container, iface string) (net.IP, error) {
- devs, err := devicesInfo(ctx, d)
- if err != nil {
- return net.IP{}, err
- }
- info := devs[iface]
- if info.IPv6Addr != nil {
- return info.IPv6Addr, nil
- }
- if info.MAC == nil {
- return nil, fmt.Errorf("unable to find MAC address of %s", iface)
- }
- if logs, err := d.Exec(ctx, dockerutil.ExecOpts{}, "ip", "addr", "add", netdevs.MACToIP(info.MAC).String(), "scope", "link", "dev", iface); err != nil {
- return net.IP{}, fmt.Errorf("unable to ip addr add on container %s: %w, logs: %s", d.Name, err, logs)
- }
- // Now try again, to make sure that it worked.
- devs, err = devicesInfo(ctx, d)
- if err != nil {
- return net.IP{}, err
- }
- info = devs[iface]
- if info.IPv6Addr == nil {
- return net.IP{}, fmt.Errorf("unable to set IPv6 address on container %s", d.Name)
- }
- return info.IPv6Addr, nil
-}
-
-// createDockerNetwork makes a randomly-named network that will start with the
-// namePrefix. The network will be a random /24 subnet.
-func createDockerNetwork(ctx context.Context, n *dockerutil.Network) error {
- randSource := rand.NewSource(time.Now().UnixNano())
- r1 := rand.New(randSource)
- // Class C, 192.0.0.0 to 223.255.255.255, transitionally has mask 24.
- ip := net.IPv4(byte(r1.Intn(224-192)+192), byte(r1.Intn(256)), byte(r1.Intn(256)), 0)
- n.Subnet = &net.IPNet{
- IP: ip,
- Mask: ip.DefaultMask(),
- }
- return n.Create(ctx)
-}
-
-// StartContainer will create a container instance from runOpts, connect it
-// with the specified docker networks and start executing the specified cmd.
-func StartContainer(ctx context.Context, runOpts dockerutil.RunOpts, c *dockerutil.Container, containerAddr net.IP, ns []*dockerutil.Network, sysctls map[string]string, cmd ...string) error {
- conf, hostconf, netconf := c.ConfigsFrom(runOpts, cmd...)
- _ = netconf
- hostconf.Sysctls = map[string]string{"net.ipv6.conf.all.disable_ipv6": "0"}
- for k, v := range sysctls {
- hostconf.Sysctls[k] = v
- }
-
- if err := c.CreateFrom(ctx, runOpts.Image, conf, hostconf, nil); err != nil {
- return fmt.Errorf("unable to create container %s: %w", c.Name, err)
- }
-
- if err := AddNetworks(ctx, c, containerAddr, ns); err != nil {
- return fmt.Errorf("unable to connect the container with the networks: %w", err)
- }
-
- if err := c.Start(ctx); err != nil {
- return fmt.Errorf("unable to start container %s: %w", c.Name, err)
- }
- return nil
-}
-
-// MountTempDirectory creates a temporary directory on host with the template
-// and then mounts it into the container under the name provided. The temporary
-// directory name is returned. Content in that directory will be copied to
-// TEST_UNDECLARED_OUTPUTS_DIR in cleanup phase.
-func MountTempDirectory(t *testing.T, runOpts *dockerutil.RunOpts, hostDirTemplate, containerDir string) (string, error) {
- t.Helper()
- tmpDir, err := ioutil.TempDir("", hostDirTemplate)
- if err != nil {
- return "", fmt.Errorf("failed to create a temp dir: %w", err)
- }
- t.Cleanup(func() {
- if err := exec.Command("/bin/cp", "-r", tmpDir, os.Getenv("TEST_UNDECLARED_OUTPUTS_DIR")).Run(); err != nil {
- t.Errorf("unable to copy container output files: %s", err)
- }
- if err := os.RemoveAll(tmpDir); err != nil {
- t.Errorf("failed to remove tmpDir %s: %s", tmpDir, err)
- }
- })
- runOpts.Mounts = append(runOpts.Mounts, mount.Mount{
- Type: mount.TypeBind,
- Source: tmpDir,
- Target: containerDir,
- ReadOnly: false,
- })
- return tmpDir, nil
-}
diff --git a/test/packetimpact/runner/packetimpact_test.go b/test/packetimpact/runner/packetimpact_test.go
deleted file mode 100644
index 46334b7ab..000000000
--- a/test/packetimpact/runner/packetimpact_test.go
+++ /dev/null
@@ -1,32 +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.
-
-// The runner starts docker containers and networking for a packetimpact test.
-package packetimpact_test
-
-import (
- "context"
- "flag"
- "testing"
-
- "gvisor.dev/gvisor/test/packetimpact/runner"
-)
-
-func init() {
- runner.RegisterFlags(flag.CommandLine)
-}
-
-func TestOne(t *testing.T) {
- runner.TestWithDUT(context.Background(), t, runner.NewDockerDUT)
-}
diff --git a/test/packetimpact/testbench/BUILD b/test/packetimpact/testbench/BUILD
deleted file mode 100644
index d8059ab98..000000000
--- a/test/packetimpact/testbench/BUILD
+++ /dev/null
@@ -1,47 +0,0 @@
-load("//tools:defs.bzl", "go_library", "go_test")
-
-package(
- licenses = ["notice"],
-)
-
-go_library(
- name = "testbench",
- srcs = [
- "connections.go",
- "dut.go",
- "dut_client.go",
- "layers.go",
- "rawsockets.go",
- "testbench.go",
- ],
- visibility = ["//test/packetimpact:__subpackages__"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/binary",
- "//pkg/hostarch",
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/header",
- "//pkg/tcpip/seqnum",
- "//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",
- "//pkg/tcpip/header",
- "@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 ed56f9ac7..000000000
--- a/test/packetimpact/testbench/connections.go
+++ /dev/null
@@ -1,1263 +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 (
- "fmt"
- "math/rand"
- "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"
-)
-
-func portFromSockaddr(sa unix.Sockaddr) (uint16, error) {
- switch sa := sa.(type) {
- case *unix.SockaddrInet4:
- return uint16(sa.Port), nil
- case *unix.SockaddrInet6:
- return uint16(sa.Port), nil
- }
- return 0, fmt.Errorf("sockaddr type %T does not contain port", sa)
-}
-
-// 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 (n *DUTTestNet) pickPort(domain, typ int) (fd int, port uint16, err error) {
- fd, err = unix.Socket(domain, typ, 0)
- if err != nil {
- return -1, 0, fmt.Errorf("creating socket: %w", err)
- }
- defer func() {
- if err != nil {
- if cerr := unix.Close(fd); cerr != nil {
- err = multierr.Append(err, fmt.Errorf("failed to close socket %d: %w", fd, cerr))
- }
- }
- }()
- var sa unix.Sockaddr
- switch domain {
- case unix.AF_INET:
- var sa4 unix.SockaddrInet4
- copy(sa4.Addr[:], n.LocalIPv4)
- sa = &sa4
- case unix.AF_INET6:
- sa6 := unix.SockaddrInet6{ZoneId: n.LocalDevID}
- copy(sa6.Addr[:], n.LocalIPv6)
- 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, fmt.Errorf("binding to %+v: %w", sa, err)
- }
- sa, err = unix.Getsockname(fd)
- if err != nil {
- return -1, 0, fmt.Errorf("unix.Getsocketname(%d): %w", fd, err)
- }
- port, err = portFromSockaddr(sa)
- if err != nil {
- return -1, 0, fmt.Errorf("extracting port from socket address %+v: %w", sa, err)
- }
- return fd, port, nil
-}
-
-// 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 received. The
- // input is a Layer with all prev and next pointers populated so that the
- // entire frame as it was received 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 (n *DUTTestNet) newEtherState(out, in Ether) (*etherState, error) {
- lmac := tcpip.LinkAddress(n.LocalMAC)
- rmac := tcpip.LinkAddress(n.RemoteMAC)
- 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 (n *DUTTestNet) newIPv4State(out, in IPv4) (*ipv4State, error) {
- lIP := tcpip.Address(n.LocalIPv4)
- rIP := tcpip.Address(n.RemoteIPv4)
- 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 (n *DUTTestNet) newIPv6State(out, in IPv6) (*ipv6State, error) {
- lIP := tcpip.Address(n.LocalIPv6)
- rIP := tcpip.Address(n.RemoteIPv6)
- 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 (n *DUTTestNet) newTCPState(domain int, out, in TCP) (*tcpState, error) {
- portPickerFD, localPort, err := n.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 seq, flags := s.localSeqNum, tcpReceived.Flags; seq != nil && flags != nil && *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(*seq))
- }
- 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 (n *DUTTestNet) newUDPState(domain int, out, in UDP) (*udpState, error) {
- portPickerFD, localPort, err := n.pickPort(domain, unix.SOCK_DGRAM)
- if err != nil {
- return nil, fmt.Errorf("picking port: %w", 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
-}
-
-// 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(t *testing.T) {
- t.Helper()
-
- 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 {
- t.Fatalf("unable to close %+v: %s", conn, errs)
- }
-}
-
-// CreateFrame builds a frame for the connection with defaults overridden
-// from the innermost layer out, and additionalLayers added after it.
-//
-// Note that overrideLayers can have a length that is less than the number
-// of layers in this connection, and in such cases the innermost layers are
-// overridden first. As an example, valid values of overrideLayers for a TCP-
-// over-IPv4-over-Ethernet connection are: nil, [TCP], [IPv4, TCP], and
-// [Ethernet, IPv4, TCP].
-func (conn *Connection) CreateFrame(t *testing.T, overrideLayers Layers, additionalLayers ...Layer) Layers {
- t.Helper()
-
- var layersToSend Layers
- for i, s := range conn.layerStates {
- layer := s.outgoing()
- // overrideLayers and conn.layerStates have their tails aligned, so
- // to find the index we move backwards by the distance i is to the
- // end.
- if j := len(overrideLayers) - (len(conn.layerStates) - i); j >= 0 {
- if err := layer.merge(overrideLayers[j]); err != nil {
- t.Fatalf("can't merge %+v into %+v: %s", layer, overrideLayers[j], err)
- }
- }
- layersToSend = append(layersToSend, layer)
- }
- layersToSend = append(layersToSend, additionalLayers...)
- return layersToSend
-}
-
-// SendFrameStateless sends a frame without updating any of the layer states.
-//
-// This method is useful for sending out-of-band control messages such as
-// ICMP packets, where it would not make sense to update the transport layer's
-// state using the ICMP header.
-func (conn *Connection) SendFrameStateless(t *testing.T, frame Layers) {
- t.Helper()
-
- outBytes, err := frame.ToBytes()
- if err != nil {
- t.Fatalf("can't build outgoing packet: %s", err)
- }
- conn.injector.Send(t, outBytes)
-}
-
-// SendFrame sends a frame on the wire and updates the state of all layers.
-func (conn *Connection) SendFrame(t *testing.T, frame Layers) {
- t.Helper()
-
- outBytes, err := frame.ToBytes()
- if err != nil {
- t.Fatalf("can't build outgoing packet: %s", err)
- }
- conn.injector.Send(t, 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 {
- t.Fatalf("Unable to update the state of %+v with %s: %s", s, sentFrame[i], err)
- }
- }
-}
-
-// send sends a packet, possibly with layers of this connection overridden and
-// additional layers added.
-//
-// Types defined with Connection as the underlying type should expose
-// type-safe versions of this method.
-func (conn *Connection) send(t *testing.T, overrideLayers Layers, additionalLayers ...Layer) {
- t.Helper()
-
- conn.SendFrame(t, conn.CreateFrame(t, overrideLayers, 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(t *testing.T, timeout time.Duration) Layers {
- t.Helper()
-
- if timeout <= 0 {
- return nil
- }
- b := conn.sniffer.Recv(t, 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 expects a frame with the final layerStates layer matching the
-// provided Layer within the timeout specified. If it doesn't arrive in time,
-// an error is returned.
-func (conn *Connection) Expect(t *testing.T, layer Layer, timeout time.Duration) (Layer, error) {
- t.Helper()
-
- // 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(t, layers, timeout)
- if err != nil {
- return nil, err
- }
- if len(conn.layerStates)-1 < len(gotFrame) {
- return gotFrame[len(conn.layerStates)-1], nil
- }
- t.Fatalf("the received frame should be at least as long as the expected layers, got %d layers, want at least %d layers, got frame: %#v", len(gotFrame), len(conn.layerStates), gotFrame)
- 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(t *testing.T, layers Layers, timeout time.Duration) (Layers, error) {
- t.Helper()
-
- frames, ok := conn.ListenForFrame(t, layers, timeout)
- if ok {
- return frames[len(frames)-1], nil
- }
- if len(frames) == 0 {
- return nil, fmt.Errorf("got no frames matching %s during %s", layers, timeout)
- }
-
- var errs error
- for _, got := range frames {
- want := conn.incoming(layers)
- if err := want.merge(layers); err != nil {
- errs = multierr.Combine(errs, err)
- } else {
- errs = multierr.Combine(errs, &layersError{got: got, want: want})
- }
- }
- return nil, fmt.Errorf("got frames:\n%w want %s during %s", errs, layers, timeout)
-}
-
-// ListenForFrame captures all frames until a frame matches the provided Layers,
-// or until the timeout specified. Returns all captured frames, including the
-// matched frame, and true if the desired frame was found.
-func (conn *Connection) ListenForFrame(t *testing.T, layers Layers, timeout time.Duration) ([]Layers, bool) {
- t.Helper()
-
- deadline := time.Now().Add(timeout)
- var frames []Layers
- for {
- var got Layers
- if timeout := time.Until(deadline); timeout > 0 {
- got = conn.recvFrame(t, timeout)
- }
- if got == nil {
- return frames, false
- }
- frames = append(frames, got)
- if conn.match(layers, got) {
- for i, s := range conn.layerStates {
- if err := s.received(got[i]); err != nil {
- t.Fatalf("failed to update test connection's layer states based on received frame: %s", err)
- }
- }
- return frames, true
- }
- }
-}
-
-// Drain drains the sniffer's receive buffer by receiving packets until there's
-// nothing else to receive.
-func (conn *Connection) Drain(t *testing.T) {
- t.Helper()
-
- conn.sniffer.Drain(t)
-}
-
-// TCPIPv4 maintains the state for all the layers in a TCP/IPv4 connection.
-type TCPIPv4 struct {
- Connection
-}
-
-// NewTCPIPv4 creates a new TCPIPv4 connection with reasonable defaults.
-func (n *DUTTestNet) NewTCPIPv4(t *testing.T, outgoingTCP, incomingTCP TCP) TCPIPv4 {
- t.Helper()
-
- etherState, err := n.newEtherState(Ether{}, Ether{})
- if err != nil {
- t.Fatalf("can't make etherState: %s", err)
- }
- ipv4State, err := n.newIPv4State(IPv4{}, IPv4{})
- if err != nil {
- t.Fatalf("can't make ipv4State: %s", err)
- }
- tcpState, err := n.newTCPState(unix.AF_INET, outgoingTCP, incomingTCP)
- if err != nil {
- t.Fatalf("can't make tcpState: %s", err)
- }
- injector, err := n.NewInjector(t)
- if err != nil {
- t.Fatalf("can't make injector: %s", err)
- }
- sniffer, err := n.NewSniffer(t)
- if err != nil {
- t.Fatalf("can't make sniffer: %s", err)
- }
-
- return TCPIPv4{
- Connection: Connection{
- layerStates: []layerState{etherState, ipv4State, tcpState},
- injector: injector,
- sniffer: sniffer,
- },
- }
-}
-
-// Connect performs a TCP 3-way handshake. The input Connection should have a
-// final TCP Layer.
-func (conn *TCPIPv4) Connect(t *testing.T) {
- t.Helper()
-
- // Send the SYN.
- conn.Send(t, TCP{Flags: TCPFlags(header.TCPFlagSyn)})
-
- // Wait for the SYN-ACK.
- synAck, err := conn.Expect(t, TCP{Flags: TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, time.Second)
- if err != nil {
- t.Fatalf("didn't get synack during handshake: %s", err)
- }
- conn.layerStates[len(conn.layerStates)-1].(*tcpState).synAck = synAck
-
- // Send an ACK.
- conn.Send(t, TCP{Flags: TCPFlags(header.TCPFlagAck)})
-}
-
-// ConnectWithOptions performs a TCP 3-way handshake with given TCP options.
-// The input Connection should have a final TCP Layer.
-func (conn *TCPIPv4) ConnectWithOptions(t *testing.T, options []byte) {
- t.Helper()
-
- // Send the SYN.
- conn.Send(t, TCP{Flags: TCPFlags(header.TCPFlagSyn), Options: options})
-
- // Wait for the SYN-ACK.
- synAck, err := conn.Expect(t, TCP{Flags: TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, time.Second)
- if err != nil {
- t.Fatalf("didn't get synack during handshake: %s", err)
- }
- conn.layerStates[len(conn.layerStates)-1].(*tcpState).synAck = synAck
-
- // Send an ACK.
- conn.Send(t, TCP{Flags: TCPFlags(header.TCPFlagAck)})
-}
-
-// ExpectData is a convenient method that expects a Layer and the Layer after
-// it. If it doesn't arrive in time, it returns nil.
-func (conn *TCPIPv4) ExpectData(t *testing.T, tcp *TCP, payload *Payload, timeout time.Duration) (Layers, error) {
- t.Helper()
-
- expected := make([]Layer, len(conn.layerStates))
- expected[len(expected)-1] = tcp
- if payload != nil {
- expected = append(expected, payload)
- }
- return conn.ExpectFrame(t, expected, timeout)
-}
-
-// ExpectNextData attempts to receive the next incoming segment for the
-// connection and expects that to match the given layers.
-//
-// It differs from ExpectData() in that here we are only interested in the next
-// received segment, while ExpectData() can receive multiple segments for the
-// connection until there is a match with given layers or a timeout.
-func (conn *TCPIPv4) ExpectNextData(t *testing.T, tcp *TCP, payload *Payload, timeout time.Duration) (Layers, error) {
- t.Helper()
-
- // Receive the first incoming TCP segment for this connection.
- got, err := conn.ExpectData(t, &TCP{}, nil, timeout)
- if err != nil {
- return nil, err
- }
-
- expected := make([]Layer, len(conn.layerStates))
- expected[len(expected)-1] = tcp
- if payload != nil {
- expected = append(expected, payload)
- tcp.SeqNum = Uint32(uint32(*conn.RemoteSeqNum(t)) - uint32(payload.Length()))
- }
- if !conn.match(expected, got) {
- return nil, fmt.Errorf("next frame is not matching %s during %s: got %s", expected, timeout, got)
- }
- return got, nil
-}
-
-// 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(t *testing.T, tcp TCP, additionalLayers ...Layer) {
- t.Helper()
-
- conn.send(t, Layers{&tcp}, additionalLayers...)
-}
-
-// Expect expects a frame with the TCP layer matching the provided TCP within
-// the timeout specified. If it doesn't arrive in time, an error is returned.
-func (conn *TCPIPv4) Expect(t *testing.T, tcp TCP, timeout time.Duration) (*TCP, error) {
- t.Helper()
-
- layer, err := conn.Connection.Expect(t, &tcp, timeout)
- if layer == nil {
- return nil, err
- }
- gotTCP, ok := layer.(*TCP)
- if !ok {
- t.Fatalf("expected %s to be TCP", layer)
- }
- return gotTCP, err
-}
-
-func (conn *TCPIPv4) tcpState(t *testing.T) *tcpState {
- t.Helper()
-
- state, ok := conn.layerStates[2].(*tcpState)
- if !ok {
- t.Fatalf("got transport-layer state type=%T, expected tcpState", conn.layerStates[2])
- }
- return state
-}
-
-func (conn *TCPIPv4) ipv4State(t *testing.T) *ipv4State {
- t.Helper()
-
- state, ok := conn.layerStates[1].(*ipv4State)
- if !ok {
- t.Fatalf("expected network-layer state type=%T, expected ipv4State", conn.layerStates[1])
- }
- return state
-}
-
-// RemoteSeqNum returns the next expected sequence number from the DUT.
-func (conn *TCPIPv4) RemoteSeqNum(t *testing.T) *seqnum.Value {
- t.Helper()
-
- return conn.tcpState(t).remoteSeqNum
-}
-
-// LocalSeqNum returns the next sequence number to send from the testbench.
-func (conn *TCPIPv4) LocalSeqNum(t *testing.T) *seqnum.Value {
- t.Helper()
-
- return conn.tcpState(t).localSeqNum
-}
-
-// SynAck returns the SynAck that was part of the handshake.
-func (conn *TCPIPv4) SynAck(t *testing.T) *TCP {
- t.Helper()
-
- return conn.tcpState(t).synAck
-}
-
-// LocalAddr gets the local socket address of this connection.
-func (conn *TCPIPv4) LocalAddr(t *testing.T) *unix.SockaddrInet4 {
- t.Helper()
-
- sa := &unix.SockaddrInet4{Port: int(*conn.tcpState(t).out.SrcPort)}
- copy(sa.Addr[:], *conn.ipv4State(t).out.SrcAddr)
- return sa
-}
-
-// GenerateOTWSeqSegment generates a 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(t *testing.T, conn *TCPIPv4, seqNumOffset seqnum.Size, windowSize seqnum.Size) TCP {
- t.Helper()
- lastAcceptable := conn.LocalSeqNum(t).Add(windowSize)
- otwSeq := uint32(lastAcceptable.Add(seqNumOffset))
- return TCP{SeqNum: Uint32(otwSeq), Flags: TCPFlags(header.TCPFlagAck)}
-}
-
-// GenerateUnaccACKSegment generates a 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(t *testing.T, conn *TCPIPv4, seqNumOffset seqnum.Size, windowSize seqnum.Size) TCP {
- t.Helper()
- lastAcceptable := conn.RemoteSeqNum(t)
- unaccAck := uint32(lastAcceptable.Add(seqNumOffset))
- return TCP{AckNum: Uint32(unaccAck), Flags: TCPFlags(header.TCPFlagAck)}
-}
-
-// IPv4Conn maintains the state for all the layers in a IPv4 connection.
-type IPv4Conn struct {
- Connection
-}
-
-// NewIPv4Conn creates a new IPv4Conn connection with reasonable defaults.
-func (n *DUTTestNet) NewIPv4Conn(t *testing.T, outgoingIPv4, incomingIPv4 IPv4) IPv4Conn {
- t.Helper()
-
- etherState, err := n.newEtherState(Ether{}, Ether{})
- if err != nil {
- t.Fatalf("can't make EtherState: %s", err)
- }
- ipv4State, err := n.newIPv4State(outgoingIPv4, incomingIPv4)
- if err != nil {
- t.Fatalf("can't make IPv4State: %s", err)
- }
-
- injector, err := n.NewInjector(t)
- if err != nil {
- t.Fatalf("can't make injector: %s", err)
- }
- sniffer, err := n.NewSniffer(t)
- if err != nil {
- t.Fatalf("can't make sniffer: %s", err)
- }
-
- return IPv4Conn{
- Connection: Connection{
- layerStates: []layerState{etherState, ipv4State},
- injector: injector,
- sniffer: sniffer,
- },
- }
-}
-
-// Send sends a frame with ipv4 overriding the IPv4 layer defaults and
-// additionalLayers added after it.
-func (c *IPv4Conn) Send(t *testing.T, ipv4 IPv4, additionalLayers ...Layer) {
- t.Helper()
-
- c.send(t, Layers{&ipv4}, additionalLayers...)
-}
-
-// IPv6Conn maintains the state for all the layers in a IPv6 connection.
-type IPv6Conn struct {
- Connection
-}
-
-// NewIPv6Conn creates a new IPv6Conn connection with reasonable defaults.
-func (n *DUTTestNet) NewIPv6Conn(t *testing.T, outgoingIPv6, incomingIPv6 IPv6) IPv6Conn {
- t.Helper()
-
- etherState, err := n.newEtherState(Ether{}, Ether{})
- if err != nil {
- t.Fatalf("can't make EtherState: %s", err)
- }
- ipv6State, err := n.newIPv6State(outgoingIPv6, incomingIPv6)
- if err != nil {
- t.Fatalf("can't make IPv6State: %s", err)
- }
-
- injector, err := n.NewInjector(t)
- if err != nil {
- t.Fatalf("can't make injector: %s", err)
- }
- sniffer, err := n.NewSniffer(t)
- if err != nil {
- t.Fatalf("can't make sniffer: %s", err)
- }
-
- return IPv6Conn{
- Connection: Connection{
- layerStates: []layerState{etherState, ipv6State},
- injector: injector,
- sniffer: sniffer,
- },
- }
-}
-
-// Send sends a frame with ipv6 overriding the IPv6 layer defaults and
-// additionalLayers added after it.
-func (conn *IPv6Conn) Send(t *testing.T, ipv6 IPv6, additionalLayers ...Layer) {
- t.Helper()
-
- conn.send(t, Layers{&ipv6}, additionalLayers...)
-}
-
-// UDPIPv4 maintains the state for all the layers in a UDP/IPv4 connection.
-type UDPIPv4 struct {
- Connection
-}
-
-// NewUDPIPv4 creates a new UDPIPv4 connection with reasonable defaults.
-func (n *DUTTestNet) NewUDPIPv4(t *testing.T, outgoingUDP, incomingUDP UDP) UDPIPv4 {
- t.Helper()
-
- etherState, err := n.newEtherState(Ether{}, Ether{})
- if err != nil {
- t.Fatalf("can't make etherState: %s", err)
- }
- ipv4State, err := n.newIPv4State(IPv4{}, IPv4{})
- if err != nil {
- t.Fatalf("can't make ipv4State: %s", err)
- }
- udpState, err := n.newUDPState(unix.AF_INET, outgoingUDP, incomingUDP)
- if err != nil {
- t.Fatalf("can't make udpState: %s", err)
- }
- injector, err := n.NewInjector(t)
- if err != nil {
- t.Fatalf("can't make injector: %s", err)
- }
- sniffer, err := n.NewSniffer(t)
- if err != nil {
- t.Fatalf("can't make sniffer: %s", err)
- }
-
- return UDPIPv4{
- Connection: Connection{
- layerStates: []layerState{etherState, ipv4State, udpState},
- injector: injector,
- sniffer: sniffer,
- },
- }
-}
-
-func (conn *UDPIPv4) udpState(t *testing.T) *udpState {
- t.Helper()
-
- state, ok := conn.layerStates[2].(*udpState)
- if !ok {
- t.Fatalf("got transport-layer state type=%T, expected udpState", conn.layerStates[2])
- }
- return state
-}
-
-func (conn *UDPIPv4) ipv4State(t *testing.T) *ipv4State {
- t.Helper()
-
- state, ok := conn.layerStates[1].(*ipv4State)
- if !ok {
- t.Fatalf("got network-layer state type=%T, expected ipv4State", conn.layerStates[1])
- }
- return state
-}
-
-// LocalAddr gets the local socket address of this connection.
-func (conn *UDPIPv4) LocalAddr(t *testing.T) *unix.SockaddrInet4 {
- t.Helper()
-
- sa := &unix.SockaddrInet4{Port: int(*conn.udpState(t).out.SrcPort)}
- copy(sa.Addr[:], *conn.ipv4State(t).out.SrcAddr)
- return sa
-}
-
-// SrcPort returns the source port of this connection.
-func (conn *UDPIPv4) SrcPort(t *testing.T) uint16 {
- t.Helper()
-
- return *conn.udpState(t).out.SrcPort
-}
-
-// Send sends a packet with reasonable defaults, potentially overriding the UDP
-// layer and adding additionLayers.
-func (conn *UDPIPv4) Send(t *testing.T, udp UDP, additionalLayers ...Layer) {
- t.Helper()
-
- conn.send(t, Layers{&udp}, additionalLayers...)
-}
-
-// SendIP sends a packet with reasonable defaults, potentially overriding the
-// UDP and IPv4 headers and adding additionLayers.
-func (conn *UDPIPv4) SendIP(t *testing.T, ip IPv4, udp UDP, additionalLayers ...Layer) {
- t.Helper()
-
- conn.send(t, Layers{&ip, &udp}, additionalLayers...)
-}
-
-// SendFrame sends a frame on the wire and updates the state of all layers.
-func (conn *UDPIPv4) SendFrame(t *testing.T, overrideLayers Layers, additionalLayers ...Layer) {
- t.Helper()
-
- conn.send(t, overrideLayers, additionalLayers...)
-}
-
-// Expect expects a frame with the UDP layer matching the provided UDP within
-// the timeout specified. If it doesn't arrive in time, an error is returned.
-func (conn *UDPIPv4) Expect(t *testing.T, udp UDP, timeout time.Duration) (*UDP, error) {
- t.Helper()
-
- layer, err := conn.Connection.Expect(t, &udp, timeout)
- if err != nil {
- return nil, err
- }
- gotUDP, ok := layer.(*UDP)
- if !ok {
- t.Fatalf("expected %s to be UDP", layer)
- }
- return gotUDP, nil
-}
-
-// ExpectData is a convenient method that expects a Layer and the Layer after
-// it. If it doesn't arrive in time, it returns nil.
-func (conn *UDPIPv4) ExpectData(t *testing.T, udp UDP, payload Payload, timeout time.Duration) (Layers, error) {
- t.Helper()
-
- expected := make([]Layer, len(conn.layerStates))
- expected[len(expected)-1] = &udp
- if payload.length() != 0 {
- expected = append(expected, &payload)
- }
- return conn.ExpectFrame(t, expected, timeout)
-}
-
-// UDPIPv6 maintains the state for all the layers in a UDP/IPv6 connection.
-type UDPIPv6 struct {
- Connection
-}
-
-// NewUDPIPv6 creates a new UDPIPv6 connection with reasonable defaults.
-func (n *DUTTestNet) NewUDPIPv6(t *testing.T, outgoingUDP, incomingUDP UDP) UDPIPv6 {
- t.Helper()
-
- etherState, err := n.newEtherState(Ether{}, Ether{})
- if err != nil {
- t.Fatalf("can't make etherState: %s", err)
- }
- ipv6State, err := n.newIPv6State(IPv6{}, IPv6{})
- if err != nil {
- t.Fatalf("can't make IPv6State: %s", err)
- }
- udpState, err := n.newUDPState(unix.AF_INET6, outgoingUDP, incomingUDP)
- if err != nil {
- t.Fatalf("can't make udpState: %s", err)
- }
- injector, err := n.NewInjector(t)
- if err != nil {
- t.Fatalf("can't make injector: %s", err)
- }
- sniffer, err := n.NewSniffer(t)
- if err != nil {
- t.Fatalf("can't make sniffer: %s", err)
- }
- return UDPIPv6{
- Connection: Connection{
- layerStates: []layerState{etherState, ipv6State, udpState},
- injector: injector,
- sniffer: sniffer,
- },
- }
-}
-
-func (conn *UDPIPv6) udpState(t *testing.T) *udpState {
- t.Helper()
-
- state, ok := conn.layerStates[2].(*udpState)
- if !ok {
- t.Fatalf("got transport-layer state type=%T, expected udpState", conn.layerStates[2])
- }
- return state
-}
-
-func (conn *UDPIPv6) ipv6State(t *testing.T) *ipv6State {
- t.Helper()
-
- state, ok := conn.layerStates[1].(*ipv6State)
- if !ok {
- t.Fatalf("got network-layer state type=%T, expected ipv6State", conn.layerStates[1])
- }
- return state
-}
-
-// LocalAddr gets the local socket address of this connection.
-func (conn *UDPIPv6) LocalAddr(t *testing.T, zoneID uint32) *unix.SockaddrInet6 {
- t.Helper()
-
- sa := &unix.SockaddrInet6{
- Port: int(*conn.udpState(t).out.SrcPort),
- // Local address is in perspective to the remote host, so it's scoped to the
- // ID of the remote interface.
- ZoneId: zoneID,
- }
- copy(sa.Addr[:], *conn.ipv6State(t).out.SrcAddr)
- return sa
-}
-
-// SrcPort returns the source port of this connection.
-func (conn *UDPIPv6) SrcPort(t *testing.T) uint16 {
- t.Helper()
-
- return *conn.udpState(t).out.SrcPort
-}
-
-// Send sends a packet with reasonable defaults, potentially overriding the UDP
-// layer and adding additionLayers.
-func (conn *UDPIPv6) Send(t *testing.T, udp UDP, additionalLayers ...Layer) {
- t.Helper()
-
- conn.send(t, Layers{&udp}, additionalLayers...)
-}
-
-// SendIPv6 sends a packet with reasonable defaults, potentially overriding the
-// UDP and IPv6 headers and adding additionLayers.
-func (conn *UDPIPv6) SendIPv6(t *testing.T, ip IPv6, udp UDP, additionalLayers ...Layer) {
- t.Helper()
-
- conn.send(t, Layers{&ip, &udp}, additionalLayers...)
-}
-
-// SendFrame sends a frame on the wire and updates the state of all layers.
-func (conn *UDPIPv6) SendFrame(t *testing.T, overrideLayers Layers, additionalLayers ...Layer) {
- conn.send(t, overrideLayers, additionalLayers...)
-}
-
-// Expect expects a frame with the UDP layer matching the provided UDP within
-// the timeout specified. If it doesn't arrive in time, an error is returned.
-func (conn *UDPIPv6) Expect(t *testing.T, udp UDP, timeout time.Duration) (*UDP, error) {
- t.Helper()
-
- layer, err := conn.Connection.Expect(t, &udp, timeout)
- if err != nil {
- return nil, err
- }
- gotUDP, ok := layer.(*UDP)
- if !ok {
- t.Fatalf("expected %s to be UDP", layer)
- }
- return gotUDP, nil
-}
-
-// ExpectData is a convenient method that expects a Layer and the Layer after
-// it. If it doesn't arrive in time, it returns nil.
-func (conn *UDPIPv6) ExpectData(t *testing.T, udp UDP, payload Payload, timeout time.Duration) (Layers, error) {
- t.Helper()
-
- expected := make([]Layer, len(conn.layerStates))
- expected[len(expected)-1] = &udp
- if payload.length() != 0 {
- expected = append(expected, &payload)
- }
- return conn.ExpectFrame(t, expected, timeout)
-}
-
-// TCPIPv6 maintains the state for all the layers in a TCP/IPv6 connection.
-type TCPIPv6 struct {
- Connection
-}
-
-// NewTCPIPv6 creates a new TCPIPv6 connection with reasonable defaults.
-func (n *DUTTestNet) NewTCPIPv6(t *testing.T, outgoingTCP, incomingTCP TCP) TCPIPv6 {
- etherState, err := n.newEtherState(Ether{}, Ether{})
- if err != nil {
- t.Fatalf("can't make etherState: %s", err)
- }
- ipv6State, err := n.newIPv6State(IPv6{}, IPv6{})
- if err != nil {
- t.Fatalf("can't make ipv6State: %s", err)
- }
- tcpState, err := n.newTCPState(unix.AF_INET6, outgoingTCP, incomingTCP)
- if err != nil {
- t.Fatalf("can't make tcpState: %s", err)
- }
- injector, err := n.NewInjector(t)
- if err != nil {
- t.Fatalf("can't make injector: %s", err)
- }
- sniffer, err := n.NewSniffer(t)
- if err != nil {
- t.Fatalf("can't make sniffer: %s", err)
- }
-
- return TCPIPv6{
- Connection: Connection{
- layerStates: []layerState{etherState, ipv6State, tcpState},
- injector: injector,
- sniffer: sniffer,
- },
- }
-}
-
-// SrcPort returns the source port from the given Connection.
-func (conn *TCPIPv6) SrcPort() uint16 {
- state := conn.layerStates[2].(*tcpState)
- return *state.out.SrcPort
-}
-
-// ExpectData is a convenient method that expects a Layer and the Layer after
-// it. If it doesn't arrive in time, it returns nil.
-func (conn *TCPIPv6) ExpectData(t *testing.T, tcp *TCP, payload *Payload, timeout time.Duration) (Layers, error) {
- t.Helper()
-
- expected := make([]Layer, len(conn.layerStates))
- expected[len(expected)-1] = tcp
- if payload != nil {
- expected = append(expected, payload)
- }
- return conn.ExpectFrame(t, expected, timeout)
-}
diff --git a/test/packetimpact/testbench/dut.go b/test/packetimpact/testbench/dut.go
deleted file mode 100644
index 7e89ba2b3..000000000
--- a/test/packetimpact/testbench/dut.go
+++ /dev/null
@@ -1,789 +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"
- "encoding/binary"
- "fmt"
- "net"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "google.golang.org/grpc"
- "google.golang.org/grpc/keepalive"
- "gvisor.dev/gvisor/pkg/abi/linux"
- bin "gvisor.dev/gvisor/pkg/binary"
- "gvisor.dev/gvisor/pkg/hostarch"
- pb "gvisor.dev/gvisor/test/packetimpact/proto/posix_server_go_proto"
-)
-
-// DUT communicates with the DUT to force it to make POSIX calls.
-type DUT struct {
- conn *grpc.ClientConn
- posixServer POSIXClient
- Net *DUTTestNet
- Uname *DUTUname
-}
-
-// NewDUT creates a new connection with the DUT over gRPC.
-func NewDUT(t *testing.T) DUT {
- t.Helper()
- info := getDUTInfo()
- dut := info.ConnectToDUT(t)
- t.Cleanup(func() {
- dut.TearDownConnection()
- info.release()
- })
- return dut
-}
-
-// ConnectToDUT connects to DUT through gRPC.
-func (info *DUTInfo) ConnectToDUT(t *testing.T) DUT {
- t.Helper()
-
- n := info.Net
- posixServerAddress := net.JoinHostPort(n.POSIXServerIP.String(), fmt.Sprintf("%d", n.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{
- conn: conn,
- posixServer: posixServer,
- Net: n,
- Uname: info.Uname,
- }
-}
-
-// TearDownConnection closes the underlying connection.
-func (dut *DUT) TearDownConnection() {
- dut.conn.Close()
-}
-
-func (dut *DUT) sockaddrToProto(t *testing.T, sa unix.Sockaddr) *pb.Sockaddr {
- 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[:],
- },
- },
- }
- }
- t.Fatalf("can't parse Sockaddr struct: %+v", sa)
- return nil
-}
-
-func (dut *DUT) protoToSockaddr(t *testing.T, sa *pb.Sockaddr) unix.Sockaddr {
- 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())
- return &ret
- }
- t.Fatalf("can't parse Sockaddr proto: %#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(t *testing.T, typ, proto int32, addr net.IP) (int32, uint16) {
- t.Helper()
-
- var fd int32
- if addr.To4() != nil {
- fd = dut.Socket(t, unix.AF_INET, typ, proto)
- sa := unix.SockaddrInet4{}
- copy(sa.Addr[:], addr.To4())
- dut.Bind(t, fd, &sa)
- } else if addr.To16() != nil {
- fd = dut.Socket(t, unix.AF_INET6, typ, proto)
- sa := unix.SockaddrInet6{}
- copy(sa.Addr[:], addr.To16())
- sa.ZoneId = dut.Net.RemoteDevID
- dut.Bind(t, fd, &sa)
- } else {
- t.Fatalf("invalid IP address: %s", addr)
- }
- sa := dut.GetSockName(t, fd)
- var port int
- switch s := sa.(type) {
- case *unix.SockaddrInet4:
- port = s.Port
- case *unix.SockaddrInet6:
- port = s.Port
- default:
- 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(t *testing.T, typ, proto, backlog int32) (int32, uint16) {
- t.Helper()
-
- fd, remotePort := dut.CreateBoundSocket(t, typ, proto, dut.Net.RemoteIPv4)
- dut.Listen(t, 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(t *testing.T, sockfd int32) (int32, unix.Sockaddr) {
- t.Helper()
-
- fd, sa, err := dut.AcceptWithErrno(context.Background(), t, sockfd)
- if fd < 0 {
- t.Fatalf("failed to accept: %s", err)
- }
- return fd, sa
-}
-
-// AcceptWithErrno calls accept on the DUT.
-func (dut *DUT) AcceptWithErrno(ctx context.Context, t *testing.T, sockfd int32) (int32, unix.Sockaddr, error) {
- t.Helper()
-
- req := &pb.AcceptRequest{
- Sockfd: sockfd,
- }
- resp, err := dut.posixServer.Accept(ctx, req)
- if err != nil {
- t.Fatalf("failed to call Accept: %s", err)
- }
- return resp.GetFd(), dut.protoToSockaddr(t, resp.GetAddr()), unix.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(t *testing.T, fd int32, sa unix.Sockaddr) {
- t.Helper()
-
- ret, err := dut.BindWithErrno(context.Background(), t, fd, sa)
- if ret != 0 {
- t.Fatalf("failed to bind socket: %s", err)
- }
-}
-
-// BindWithErrno calls bind on the DUT.
-func (dut *DUT) BindWithErrno(ctx context.Context, t *testing.T, fd int32, sa unix.Sockaddr) (int32, error) {
- t.Helper()
-
- req := &pb.BindRequest{
- Sockfd: fd,
- Addr: dut.sockaddrToProto(t, sa),
- }
- resp, err := dut.posixServer.Bind(ctx, req)
- if err != nil {
- t.Fatalf("failed to call Bind: %s", err)
- }
- return resp.GetRet(), unix.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(t *testing.T, fd int32) {
- t.Helper()
-
- ret, err := dut.CloseWithErrno(context.Background(), t, fd)
- if ret != 0 {
- t.Fatalf("failed to close: %s", err)
- }
-}
-
-// CloseWithErrno calls close on the DUT.
-func (dut *DUT) CloseWithErrno(ctx context.Context, t *testing.T, fd int32) (int32, error) {
- t.Helper()
-
- req := &pb.CloseRequest{
- Fd: fd,
- }
- resp, err := dut.posixServer.Close(ctx, req)
- if err != nil {
- t.Fatalf("failed to call Close: %s", err)
- }
- return resp.GetRet(), unix.Errno(resp.GetErrno_())
-}
-
-// Connect calls connect 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 ConnectWithErrno.
-func (dut *DUT) Connect(t *testing.T, fd int32, sa unix.Sockaddr) {
- t.Helper()
-
- ret, err := dut.ConnectWithErrno(context.Background(), t, fd, sa)
- // Ignore 'operation in progress' error that can be returned when the socket
- // is non-blocking.
- if err != unix.EINPROGRESS && ret != 0 {
- t.Fatalf("failed to connect socket: %s", err)
- }
-}
-
-// ConnectWithErrno calls bind on the DUT.
-func (dut *DUT) ConnectWithErrno(ctx context.Context, t *testing.T, fd int32, sa unix.Sockaddr) (int32, error) {
- t.Helper()
-
- req := &pb.ConnectRequest{
- Sockfd: fd,
- Addr: dut.sockaddrToProto(t, sa),
- }
- resp, err := dut.posixServer.Connect(ctx, req)
- if err != nil {
- t.Fatalf("failed to call Connect: %s", err)
- }
- return resp.GetRet(), unix.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(t *testing.T, sockfd int32) unix.Sockaddr {
- t.Helper()
-
- ret, sa, err := dut.GetSockNameWithErrno(context.Background(), t, sockfd)
- if ret != 0 {
- t.Fatalf("failed to getsockname: %s", err)
- }
- return sa
-}
-
-// GetSockNameWithErrno calls getsockname on the DUT.
-func (dut *DUT) GetSockNameWithErrno(ctx context.Context, t *testing.T, sockfd int32) (int32, unix.Sockaddr, error) {
- t.Helper()
-
- req := &pb.GetSockNameRequest{
- Sockfd: sockfd,
- }
- resp, err := dut.posixServer.GetSockName(ctx, req)
- if err != nil {
- t.Fatalf("failed to call Bind: %s", err)
- }
- return resp.GetRet(), dut.protoToSockaddr(t, resp.GetAddr()), unix.Errno(resp.GetErrno_())
-}
-
-func (dut *DUT) getSockOpt(ctx context.Context, t *testing.T, sockfd, level, optname, optlen int32, typ pb.GetSockOptRequest_SockOptType) (int32, *pb.SockOptVal, error) {
- t.Helper()
-
- req := &pb.GetSockOptRequest{
- Sockfd: sockfd,
- Level: level,
- Optname: optname,
- Optlen: optlen,
- Type: typ,
- }
- resp, err := dut.posixServer.GetSockOpt(ctx, req)
- if err != nil {
- t.Fatalf("failed to call GetSockOpt: %s", err)
- }
- optval := resp.GetOptval()
- if optval == nil {
- t.Fatalf("GetSockOpt response does not contain a value")
- }
- return resp.GetRet(), optval, unix.Errno(resp.GetErrno_())
-}
-
-// GetSockOpt calls getsockopt 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 GetSockOptWithErrno. Because endianess and the width of values
-// might differ between the testbench and DUT architectures, prefer to use a
-// more specific GetSockOptXxx function.
-func (dut *DUT) GetSockOpt(t *testing.T, sockfd, level, optname, optlen int32) []byte {
- t.Helper()
-
- ret, optval, err := dut.GetSockOptWithErrno(context.Background(), t, sockfd, level, optname, optlen)
- if ret != 0 {
- t.Fatalf("failed to GetSockOpt: %s", err)
- }
- return optval
-}
-
-// GetSockOptWithErrno calls getsockopt on the DUT. Because endianess and the
-// width of values might differ between the testbench and DUT architectures,
-// prefer to use a more specific GetSockOptXxxWithErrno function.
-func (dut *DUT) GetSockOptWithErrno(ctx context.Context, t *testing.T, sockfd, level, optname, optlen int32) (int32, []byte, error) {
- t.Helper()
-
- ret, optval, errno := dut.getSockOpt(ctx, t, sockfd, level, optname, optlen, pb.GetSockOptRequest_BYTES)
- bytesval, ok := optval.Val.(*pb.SockOptVal_Bytesval)
- if !ok {
- t.Fatalf("GetSockOpt got value type: %T, want bytes", optval.Val)
- }
- return ret, bytesval.Bytesval, errno
-}
-
-// GetSockOptInt calls getsockopt 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 GetSockOptIntWithErrno.
-func (dut *DUT) GetSockOptInt(t *testing.T, sockfd, level, optname int32) int32 {
- t.Helper()
-
- ret, intval, err := dut.GetSockOptIntWithErrno(context.Background(), t, sockfd, level, optname)
- if ret != 0 {
- t.Fatalf("failed to GetSockOptInt: %s", err)
- }
- return intval
-}
-
-// GetSockOptIntWithErrno calls getsockopt with an integer optval.
-func (dut *DUT) GetSockOptIntWithErrno(ctx context.Context, t *testing.T, sockfd, level, optname int32) (int32, int32, error) {
- t.Helper()
-
- ret, optval, errno := dut.getSockOpt(ctx, t, sockfd, level, optname, 0, pb.GetSockOptRequest_INT)
- intval, ok := optval.Val.(*pb.SockOptVal_Intval)
- if !ok {
- t.Fatalf("GetSockOpt got value type: %T, want int", optval.Val)
- }
- return ret, intval.Intval, errno
-}
-
-// GetSockOptTimeval calls getsockopt 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 GetSockOptTimevalWithErrno.
-func (dut *DUT) GetSockOptTimeval(t *testing.T, sockfd, level, optname int32) unix.Timeval {
- t.Helper()
-
- ret, timeval, err := dut.GetSockOptTimevalWithErrno(context.Background(), t, sockfd, level, optname)
- if ret != 0 {
- t.Fatalf("failed to GetSockOptTimeval: %s", err)
- }
- return timeval
-}
-
-// GetSockOptTimevalWithErrno calls getsockopt and returns a timeval.
-func (dut *DUT) GetSockOptTimevalWithErrno(ctx context.Context, t *testing.T, sockfd, level, optname int32) (int32, unix.Timeval, error) {
- t.Helper()
-
- ret, optval, errno := dut.getSockOpt(ctx, t, sockfd, level, optname, 0, pb.GetSockOptRequest_TIME)
- tv, ok := optval.Val.(*pb.SockOptVal_Timeval)
- if !ok {
- t.Fatalf("GetSockOpt got value type: %T, want timeval", optval.Val)
- }
- timeval := unix.Timeval{
- Sec: tv.Timeval.Seconds,
- Usec: tv.Timeval.Microseconds,
- }
- return ret, timeval, errno
-}
-
-// GetSockOptTCPInfo retreives TCPInfo for the given socket descriptor.
-func (dut *DUT) GetSockOptTCPInfo(t *testing.T, sockfd int32) linux.TCPInfo {
- t.Helper()
-
- ret, info, err := dut.GetSockOptTCPInfoWithErrno(context.Background(), t, sockfd)
- if ret != 0 || err != unix.Errno(0) {
- t.Fatalf("failed to GetSockOptTCPInfo: %s", err)
- }
- return info
-}
-
-// GetSockOptTCPInfoWithErrno retreives TCPInfo with any errno.
-func (dut *DUT) GetSockOptTCPInfoWithErrno(ctx context.Context, t *testing.T, sockfd int32) (int32, linux.TCPInfo, error) {
- t.Helper()
-
- info := linux.TCPInfo{}
- ret, infoBytes, errno := dut.GetSockOptWithErrno(ctx, t, sockfd, unix.SOL_TCP, unix.TCP_INFO, int32(linux.SizeOfTCPInfo))
- if got, want := len(infoBytes), linux.SizeOfTCPInfo; got != want {
- t.Fatalf("expected %T, got %d bytes want %d bytes", info, got, want)
- }
- bin.Unmarshal(infoBytes, hostarch.ByteOrder, &info)
-
- return ret, info, errno
-}
-
-// 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(t *testing.T, sockfd, backlog int32) {
- t.Helper()
-
- ret, err := dut.ListenWithErrno(context.Background(), t, sockfd, backlog)
- if ret != 0 {
- t.Fatalf("failed to listen: %s", err)
- }
-}
-
-// ListenWithErrno calls listen on the DUT.
-func (dut *DUT) ListenWithErrno(ctx context.Context, t *testing.T, sockfd, backlog int32) (int32, error) {
- t.Helper()
-
- req := &pb.ListenRequest{
- Sockfd: sockfd,
- Backlog: backlog,
- }
- resp, err := dut.posixServer.Listen(ctx, req)
- if err != nil {
- t.Fatalf("failed to call Listen: %s", err)
- }
- return resp.GetRet(), unix.Errno(resp.GetErrno_())
-}
-
-// PollOne calls poll on the DUT and asserts that the expected event must be
-// signaled on the given fd within the given timeout.
-func (dut *DUT) PollOne(t *testing.T, fd int32, events int16, timeout time.Duration) {
- t.Helper()
-
- pfds := dut.Poll(t, []unix.PollFd{{Fd: fd, Events: events}}, timeout)
- if n := len(pfds); n != 1 {
- t.Fatalf("Poll returned %d ready file descriptors, expected 1", n)
- }
- if readyFd := pfds[0].Fd; readyFd != fd {
- t.Fatalf("Poll returned an fd %d that was not requested (%d)", readyFd, fd)
- }
- if got, want := pfds[0].Revents, int16(events); got&want != want {
- t.Fatalf("Poll returned events does not include all of the interested events, got: %#b, want: %#b", got, want)
- }
-}
-
-// Poll calls poll on the DUT and causes a fatal test failure if it doesn't
-// succeed. If more control over error handling is needed, use PollWithErrno.
-// Only pollfds with non-empty revents are returned, the only way to tie the
-// response back to the original request is using the fd number.
-func (dut *DUT) Poll(t *testing.T, pfds []unix.PollFd, timeout time.Duration) []unix.PollFd {
- t.Helper()
-
- ret, result, err := dut.PollWithErrno(context.Background(), t, pfds, timeout)
- if ret < 0 {
- t.Fatalf("failed to poll: %s", err)
- }
- return result
-}
-
-// PollWithErrno calls poll on the DUT.
-func (dut *DUT) PollWithErrno(ctx context.Context, t *testing.T, pfds []unix.PollFd, timeout time.Duration) (int32, []unix.PollFd, error) {
- t.Helper()
-
- req := &pb.PollRequest{
- TimeoutMillis: int32(timeout.Milliseconds()),
- }
- for _, pfd := range pfds {
- req.Pfds = append(req.Pfds, &pb.PollFd{
- Fd: pfd.Fd,
- Events: uint32(pfd.Events),
- })
- }
- resp, err := dut.posixServer.Poll(ctx, req)
- if err != nil {
- t.Fatalf("failed to call Poll: %s", err)
- }
- if ret, npfds := resp.GetRet(), len(resp.GetPfds()); ret >= 0 && int(ret) != npfds {
- t.Fatalf("nonsensical poll response: ret(%d) != len(pfds)(%d)", ret, npfds)
- }
- var result []unix.PollFd
- for _, protoPfd := range resp.GetPfds() {
- result = append(result, unix.PollFd{
- Fd: protoPfd.GetFd(),
- Revents: int16(protoPfd.GetEvents()),
- })
- }
- return resp.GetRet(), result, unix.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(t *testing.T, sockfd int32, buf []byte, flags int32) int32 {
- t.Helper()
-
- ret, err := dut.SendWithErrno(context.Background(), t, sockfd, buf, flags)
- if ret == -1 {
- t.Fatalf("failed to send: %s", err)
- }
- return ret
-}
-
-// SendWithErrno calls send on the DUT.
-func (dut *DUT) SendWithErrno(ctx context.Context, t *testing.T, sockfd int32, buf []byte, flags int32) (int32, error) {
- t.Helper()
-
- req := &pb.SendRequest{
- Sockfd: sockfd,
- Buf: buf,
- Flags: flags,
- }
- resp, err := dut.posixServer.Send(ctx, req)
- if err != nil {
- t.Fatalf("failed to call Send: %s", err)
- }
- return resp.GetRet(), unix.Errno(resp.GetErrno_())
-}
-
-// SendTo calls sendto 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
-// SendToWithErrno.
-func (dut *DUT) SendTo(t *testing.T, sockfd int32, buf []byte, flags int32, destAddr unix.Sockaddr) int32 {
- t.Helper()
-
- ret, err := dut.SendToWithErrno(context.Background(), t, sockfd, buf, flags, destAddr)
- if ret == -1 {
- t.Fatalf("failed to sendto: %s", err)
- }
- return ret
-}
-
-// SendToWithErrno calls sendto on the DUT.
-func (dut *DUT) SendToWithErrno(ctx context.Context, t *testing.T, sockfd int32, buf []byte, flags int32, destAddr unix.Sockaddr) (int32, error) {
- t.Helper()
-
- req := &pb.SendToRequest{
- Sockfd: sockfd,
- Buf: buf,
- Flags: flags,
- DestAddr: dut.sockaddrToProto(t, destAddr),
- }
- resp, err := dut.posixServer.SendTo(ctx, req)
- if err != nil {
- t.Fatalf("failed to call SendTo: %s", err)
- }
- return resp.GetRet(), unix.Errno(resp.GetErrno_())
-}
-
-// SetNonBlocking will set O_NONBLOCK flag for fd if nonblocking
-// is true, otherwise it will clear the flag.
-func (dut *DUT) SetNonBlocking(t *testing.T, fd int32, nonblocking bool) {
- t.Helper()
-
- req := &pb.SetNonblockingRequest{
- Fd: fd,
- Nonblocking: nonblocking,
- }
-
- resp, err := dut.posixServer.SetNonblocking(context.Background(), req)
- if err != nil {
- t.Fatalf("failed to call SetNonblocking: %s", err)
- }
- if resp.GetRet() == -1 {
- t.Fatalf("fcntl(%d, %s) failed: %s", fd, resp.GetCmd(), unix.Errno(resp.GetErrno_()))
- }
-}
-
-func (dut *DUT) setSockOpt(ctx context.Context, t *testing.T, sockfd, level, optname int32, optval *pb.SockOptVal) (int32, error) {
- t.Helper()
-
- req := &pb.SetSockOptRequest{
- Sockfd: sockfd,
- Level: level,
- Optname: optname,
- Optval: optval,
- }
- resp, err := dut.posixServer.SetSockOpt(ctx, req)
- if err != nil {
- t.Fatalf("failed to call SetSockOpt: %s", err)
- }
- return resp.GetRet(), unix.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(t *testing.T, sockfd, level, optname int32, optval []byte) {
- t.Helper()
-
- ret, err := dut.SetSockOptWithErrno(context.Background(), t, sockfd, level, optname, optval)
- if ret != 0 {
- 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, t *testing.T, sockfd, level, optname int32, optval []byte) (int32, error) {
- t.Helper()
-
- return dut.setSockOpt(ctx, t, sockfd, level, optname, &pb.SockOptVal{Val: &pb.SockOptVal_Bytesval{optval}})
-}
-
-// 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(t *testing.T, sockfd, level, optname, optval int32) {
- t.Helper()
-
- ret, err := dut.SetSockOptIntWithErrno(context.Background(), t, sockfd, level, optname, optval)
- if ret != 0 {
- t.Fatalf("failed to SetSockOptInt: %s", err)
- }
-}
-
-// SetSockOptIntWithErrno calls setsockopt with an integer optval.
-func (dut *DUT) SetSockOptIntWithErrno(ctx context.Context, t *testing.T, sockfd, level, optname, optval int32) (int32, error) {
- t.Helper()
-
- return dut.setSockOpt(ctx, t, sockfd, level, optname, &pb.SockOptVal{Val: &pb.SockOptVal_Intval{optval}})
-}
-
-// 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(t *testing.T, sockfd, level, optname int32, tv *unix.Timeval) {
- t.Helper()
-
- ret, err := dut.SetSockOptTimevalWithErrno(context.Background(), t, sockfd, level, optname, tv)
- if ret != 0 {
- t.Fatalf("failed to SetSockOptTimeval: %s", err)
- }
-}
-
-// SetSockOptTimevalWithErrno calls setsockopt with the timeval converted to
-// bytes.
-func (dut *DUT) SetSockOptTimevalWithErrno(ctx context.Context, t *testing.T, sockfd, level, optname int32, tv *unix.Timeval) (int32, error) {
- t.Helper()
-
- timeval := pb.Timeval{
- Seconds: int64(tv.Sec),
- Microseconds: int64(tv.Usec),
- }
- return dut.setSockOpt(ctx, t, sockfd, level, optname, &pb.SockOptVal{Val: &pb.SockOptVal_Timeval{&timeval}})
-}
-
-// Socket calls socket on the DUT and returns the file descriptor. If socket
-// fails on the DUT, the test ends.
-func (dut *DUT) Socket(t *testing.T, domain, typ, proto int32) int32 {
- t.Helper()
-
- fd, err := dut.SocketWithErrno(t, domain, typ, proto)
- if fd < 0 {
- 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(t *testing.T, domain, typ, proto int32) (int32, error) {
- t.Helper()
-
- req := &pb.SocketRequest{
- Domain: domain,
- Type: typ,
- Protocol: proto,
- }
- resp, err := dut.posixServer.Socket(context.Background(), req)
- if err != nil {
- t.Fatalf("failed to call Socket: %s", err)
- }
- return resp.GetFd(), unix.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(t *testing.T, sockfd, len, flags int32) []byte {
- t.Helper()
-
- ret, buf, err := dut.RecvWithErrno(context.Background(), t, sockfd, len, flags)
- if ret == -1 {
- t.Fatalf("failed to recv: %s", err)
- }
- return buf
-}
-
-// RecvWithErrno calls recv on the DUT.
-func (dut *DUT) RecvWithErrno(ctx context.Context, t *testing.T, sockfd, len, flags int32) (int32, []byte, error) {
- t.Helper()
-
- req := &pb.RecvRequest{
- Sockfd: sockfd,
- Len: len,
- Flags: flags,
- }
- resp, err := dut.posixServer.Recv(ctx, req)
- if err != nil {
- t.Fatalf("failed to call Recv: %s", err)
- }
- return resp.GetRet(), resp.GetBuf(), unix.Errno(resp.GetErrno_())
-}
-
-// SetSockLingerOption sets SO_LINGER socket option on the DUT.
-func (dut *DUT) SetSockLingerOption(t *testing.T, sockfd int32, timeout time.Duration, enable bool) {
- var linger unix.Linger
- if enable {
- linger.Onoff = 1
- }
- linger.Linger = int32(timeout / time.Second)
-
- buf := make([]byte, 8)
- binary.LittleEndian.PutUint32(buf, uint32(linger.Onoff))
- binary.LittleEndian.PutUint32(buf[4:], uint32(linger.Linger))
- dut.SetSockOpt(t, sockfd, unix.SOL_SOCKET, unix.SO_LINGER, buf)
-}
-
-// Shutdown calls shutdown 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 ShutdownWithErrno.
-func (dut *DUT) Shutdown(t *testing.T, fd, how int32) {
- t.Helper()
-
- ret, err := dut.ShutdownWithErrno(context.Background(), t, fd, how)
- if ret != 0 {
- t.Fatalf("failed to shutdown(%d, %d): %s", fd, how, err)
- }
-}
-
-// ShutdownWithErrno calls shutdown on the DUT.
-func (dut *DUT) ShutdownWithErrno(ctx context.Context, t *testing.T, fd, how int32) (int32, error) {
- t.Helper()
-
- req := &pb.ShutdownRequest{
- Fd: fd,
- How: how,
- }
- resp, err := dut.posixServer.Shutdown(ctx, req)
- if err != nil {
- t.Fatalf("failed to call Shutdown: %s", err)
- }
- if resp.GetErrno_() == 0 {
- return resp.GetRet(), nil
- }
- return resp.GetRet(), unix.Errno(resp.GetErrno_())
-}
diff --git a/test/packetimpact/testbench/dut_client.go b/test/packetimpact/testbench/dut_client.go
deleted file mode 100644
index 3b69b28aa..000000000
--- a/test/packetimpact/testbench/dut_client.go
+++ /dev/null
@@ -1,31 +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.
-
-//go:build go1.1
-// +build go1.1
-
-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 2644b3248..000000000
--- a/test/packetimpact/testbench/layers.go
+++ /dev/null
@@ -1,1602 +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"
- "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
-}
-
-// Prev returns the previous layer.
-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
- Options *header.IPv4Options
-}
-
-func (l *IPv4) String() string {
- return stringLayer(l)
-}
-
-// ToBytes implements Layer.ToBytes.
-func (l *IPv4) ToBytes() ([]byte, error) {
- // An IPv4 header is variable length depending on the size of the Options.
- hdrLen := header.IPv4MinimumSize
- if l.Options != nil {
- if len(*l.Options)%4 != 0 {
- return nil, fmt.Errorf("invalid header options '%x (len=%d)'; must be 32 bit aligned", *l.Options, len(*l.Options))
- }
- hdrLen += len(*l.Options)
- if hdrLen > header.IPv4MaximumHeaderSize {
- return nil, fmt.Errorf("IPv4 Options %d bytes, Max %d", len(*l.Options), header.IPv4MaximumOptionsSize)
- }
- }
- b := make([]byte, hdrLen)
- h := header.IPv4(b)
- fields := &header.IPv4Fields{
- TOS: 0,
- TotalLength: 0,
- ID: 0,
- Flags: 0,
- FragmentOffset: 0,
- TTL: 64,
- Protocol: 0,
- Checksum: 0,
- SrcAddr: tcpip.Address(""),
- DstAddr: tcpip.Address(""),
- Options: nil,
- }
- 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)
- case *ICMPv4:
- fields.Protocol = uint8(header.ICMPv4ProtocolNumber)
- default:
- // We can add support for 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
- }
-
- h.Encode(fields)
-
- // Put raw option bytes from test definition in header. Options as raw bytes
- // allows us to serialize malformed options, which is not possible with
- // the provided serialization functions.
- if l.Options != nil {
- h.SetHeaderLength(h.HeaderLength() + uint8(len(*l.Options)))
- if got, want := copy(h.Options(), *l.Options), len(*l.Options); got != want {
- return nil, fmt.Errorf("failed to copy option bytes into header, got %d want %d", got, want)
- }
- }
-
- // Encode cannot set this incorrectly so we need to overwrite what it wrote
- // in order to test handling of a bad IHL value.
- if l.IHL != nil {
- h.SetHeaderLength(*l.IHL)
- }
-
- if l.Checksum == nil {
- h.SetChecksum(^h.CalculateChecksum())
- } else {
- h.SetChecksum(*l.Checksum)
- }
-
- 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
-}
-
-// TCPFlags is a helper routine that allocates a new
-// header.TCPFlags value to store v and returns a pointer to it.
-func TCPFlags(v header.TCPFlags) *header.TCPFlags {
- 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)
- options := h.Options()
- 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()),
- Options: &options,
- }
- var nextParser layerParser
- // If it is a fragment, don't treat it as having a transport protocol.
- if h.FragmentOffset() != 0 || h.More() {
- return &ipv4, parsePayload
- }
- switch h.TransportProtocol() {
- case header.TCPProtocolNumber:
- nextParser = parseTCP
- case header.UDPProtocolNumber:
- nextParser = parseUDP
- case header.ICMPv4ProtocolNumber:
- nextParser = parseICMPv4
- 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.TransportProtocol = tcpip.TransportProtocolNumber(*l.NextHeader)
- } else {
- nh, err := nextHeaderByLayer(l.next())
- if err != nil {
- return nil, err
- }
- fields.TransportProtocol = tcpip.TransportProtocolNumber(nh)
- }
- 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
-}
-
-// nextIPv6PayloadParser finds the corresponding parser for nextHeader.
-func nextIPv6PayloadParser(nextHeader uint8) layerParser {
- switch tcpip.TransportProtocolNumber(nextHeader) {
- case header.TCPProtocolNumber:
- return parseTCP
- case header.UDPProtocolNumber:
- return parseUDP
- case header.ICMPv6ProtocolNumber:
- return parseICMPv6
- }
- switch header.IPv6ExtensionHeaderIdentifier(nextHeader) {
- case header.IPv6HopByHopOptionsExtHdrIdentifier:
- return parseIPv6HopByHopOptionsExtHdr
- case header.IPv6DestinationOptionsExtHdrIdentifier:
- return parseIPv6DestinationOptionsExtHdr
- case header.IPv6FragmentExtHdrIdentifier:
- return parseIPv6FragmentExtHdr
- }
- return parsePayload
-}
-
-// 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()),
- }
- nextParser := nextIPv6PayloadParser(h.NextHeader())
- 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)
-}
-
-// IPv6HopByHopOptionsExtHdr can construct and match an IPv6HopByHopOptions
-// Extension Header.
-type IPv6HopByHopOptionsExtHdr struct {
- LayerBase
- NextHeader *header.IPv6ExtensionHeaderIdentifier
- Options []byte
-}
-
-// IPv6DestinationOptionsExtHdr can construct and match an IPv6DestinationOptions
-// Extension Header.
-type IPv6DestinationOptionsExtHdr struct {
- LayerBase
- NextHeader *header.IPv6ExtensionHeaderIdentifier
- Options []byte
-}
-
-// IPv6FragmentExtHdr can construct and match an IPv6 Fragment Extension Header.
-type IPv6FragmentExtHdr struct {
- LayerBase
- NextHeader *header.IPv6ExtensionHeaderIdentifier
- FragmentOffset *uint16
- MoreFragments *bool
- Identification *uint32
-}
-
-// nextHeaderByLayer finds the correct next header protocol value for layer l.
-func nextHeaderByLayer(l Layer) (uint8, error) {
- if l == nil {
- return uint8(header.IPv6NoNextHeaderIdentifier), nil
- }
- switch l.(type) {
- case *TCP:
- return uint8(header.TCPProtocolNumber), nil
- case *UDP:
- return uint8(header.UDPProtocolNumber), nil
- case *ICMPv6:
- return uint8(header.ICMPv6ProtocolNumber), nil
- case *Payload:
- return uint8(header.IPv6NoNextHeaderIdentifier), nil
- case *IPv6HopByHopOptionsExtHdr:
- return uint8(header.IPv6HopByHopOptionsExtHdrIdentifier), nil
- case *IPv6DestinationOptionsExtHdr:
- return uint8(header.IPv6DestinationOptionsExtHdrIdentifier), nil
- case *IPv6FragmentExtHdr:
- return uint8(header.IPv6FragmentExtHdrIdentifier), nil
- default:
- // TODO(b/161005083): Support more protocols as needed.
- return 0, fmt.Errorf("failed to deduce the IPv6 header's next protocol: %T", l)
- }
-}
-
-// ipv6OptionsExtHdrToBytes serializes an options extension header into bytes.
-func ipv6OptionsExtHdrToBytes(nextHeader *header.IPv6ExtensionHeaderIdentifier, nextLayer Layer, options []byte) ([]byte, error) {
- length := len(options) + 2
- if length%8 != 0 {
- return nil, fmt.Errorf("IPv6 extension headers must be a multiple of 8 octets long, but the length given: %d, options: %s", length, hex.Dump(options))
- }
- bytes := make([]byte, length)
- if nextHeader != nil {
- bytes[0] = byte(*nextHeader)
- } else {
- nh, err := nextHeaderByLayer(nextLayer)
- if err != nil {
- return nil, err
- }
- bytes[0] = nh
- }
- // ExtHdrLen field is the length of the extension header
- // in 8-octet unit, ignoring the first 8 octets.
- // https://tools.ietf.org/html/rfc2460#section-4.3
- // https://tools.ietf.org/html/rfc2460#section-4.6
- bytes[1] = uint8((length - 8) / 8)
- copy(bytes[2:], options)
- return bytes, nil
-}
-
-// IPv6ExtHdrIdent is a helper routine that allocates a new
-// header.IPv6ExtensionHeaderIdentifier value to store v and returns a pointer
-// to it.
-func IPv6ExtHdrIdent(id header.IPv6ExtensionHeaderIdentifier) *header.IPv6ExtensionHeaderIdentifier {
- return &id
-}
-
-// ToBytes implements Layer.ToBytes.
-func (l *IPv6HopByHopOptionsExtHdr) ToBytes() ([]byte, error) {
- return ipv6OptionsExtHdrToBytes(l.NextHeader, l.next(), l.Options)
-}
-
-// ToBytes implements Layer.ToBytes.
-func (l *IPv6DestinationOptionsExtHdr) ToBytes() ([]byte, error) {
- return ipv6OptionsExtHdrToBytes(l.NextHeader, l.next(), l.Options)
-}
-
-// ToBytes implements Layer.ToBytes.
-func (l *IPv6FragmentExtHdr) ToBytes() ([]byte, error) {
- var offset, mflag uint16
- var ident uint32
- bytes := make([]byte, header.IPv6FragmentExtHdrLength)
- if l.NextHeader != nil {
- bytes[0] = byte(*l.NextHeader)
- } else {
- nh, err := nextHeaderByLayer(l.next())
- if err != nil {
- return nil, err
- }
- bytes[0] = nh
- }
- bytes[1] = 0 // reserved
- if l.MoreFragments != nil && *l.MoreFragments {
- mflag = 1
- }
- if l.FragmentOffset != nil {
- offset = *l.FragmentOffset
- }
- if l.Identification != nil {
- ident = *l.Identification
- }
- offsetAndMflag := offset<<3 | mflag
- binary.BigEndian.PutUint16(bytes[2:], offsetAndMflag)
- binary.BigEndian.PutUint32(bytes[4:], ident)
-
- return bytes, nil
-}
-
-// parseIPv6ExtHdr parses an IPv6 extension header and returns the NextHeader
-// field, the rest of the payload and a parser function for the corresponding
-// next extension header.
-func parseIPv6ExtHdr(b []byte) (header.IPv6ExtensionHeaderIdentifier, []byte, layerParser) {
- nextHeader := b[0]
- // For HopByHop and Destination options extension headers,
- // This field is the length of the extension header in
- // 8-octet units, not including the first 8 octets.
- // https://tools.ietf.org/html/rfc2460#section-4.3
- // https://tools.ietf.org/html/rfc2460#section-4.6
- length := b[1]*8 + 8
- data := b[2:length]
- nextParser := nextIPv6PayloadParser(nextHeader)
- return header.IPv6ExtensionHeaderIdentifier(nextHeader), data, nextParser
-}
-
-// parseIPv6HopByHopOptionsExtHdr parses the bytes assuming that they start
-// with an IPv6 HopByHop Options Extension Header.
-func parseIPv6HopByHopOptionsExtHdr(b []byte) (Layer, layerParser) {
- nextHeader, options, nextParser := parseIPv6ExtHdr(b)
- return &IPv6HopByHopOptionsExtHdr{NextHeader: &nextHeader, Options: options}, nextParser
-}
-
-// parseIPv6DestinationOptionsExtHdr parses the bytes assuming that they start
-// with an IPv6 Destination Options Extension Header.
-func parseIPv6DestinationOptionsExtHdr(b []byte) (Layer, layerParser) {
- nextHeader, options, nextParser := parseIPv6ExtHdr(b)
- return &IPv6DestinationOptionsExtHdr{NextHeader: &nextHeader, Options: options}, nextParser
-}
-
-// Bool is a helper routine that allocates a new
-// bool value to store v and returns a pointer to it.
-func Bool(v bool) *bool {
- return &v
-}
-
-// parseIPv6FragmentExtHdr parses the bytes assuming that they start
-// with an IPv6 Fragment Extension Header.
-func parseIPv6FragmentExtHdr(b []byte) (Layer, layerParser) {
- nextHeader := b[0]
- var extHdr header.IPv6FragmentExtHdr
- copy(extHdr[:], b[2:])
- fragLayer := IPv6FragmentExtHdr{
- NextHeader: IPv6ExtHdrIdent(header.IPv6ExtensionHeaderIdentifier(nextHeader)),
- FragmentOffset: Uint16(extHdr.FragmentOffset()),
- MoreFragments: Bool(extHdr.More()),
- Identification: Uint32(extHdr.ID()),
- }
- // If it is a fragment, we can't interpret it.
- if extHdr.FragmentOffset() != 0 || extHdr.More() {
- return &fragLayer, parsePayload
- }
- return &fragLayer, nextIPv6PayloadParser(nextHeader)
-}
-
-func (l *IPv6HopByHopOptionsExtHdr) length() int {
- return len(l.Options) + 2
-}
-
-func (l *IPv6HopByHopOptionsExtHdr) match(other Layer) bool {
- return equalLayer(l, other)
-}
-
-// merge overrides the values in l with the values from other but only in fields
-// where the value is not nil.
-func (l *IPv6HopByHopOptionsExtHdr) merge(other Layer) error {
- return mergeLayer(l, other)
-}
-
-func (l *IPv6HopByHopOptionsExtHdr) String() string {
- return stringLayer(l)
-}
-
-func (l *IPv6DestinationOptionsExtHdr) length() int {
- return len(l.Options) + 2
-}
-
-func (l *IPv6DestinationOptionsExtHdr) match(other Layer) bool {
- return equalLayer(l, other)
-}
-
-// merge overrides the values in l with the values from other but only in fields
-// where the value is not nil.
-func (l *IPv6DestinationOptionsExtHdr) merge(other Layer) error {
- return mergeLayer(l, other)
-}
-
-func (l *IPv6DestinationOptionsExtHdr) String() string {
- return stringLayer(l)
-}
-
-func (*IPv6FragmentExtHdr) length() int {
- return header.IPv6FragmentExtHdrLength
-}
-
-func (l *IPv6FragmentExtHdr) match(other Layer) bool {
- return equalLayer(l, other)
-}
-
-// merge overrides the values in l with the values from other but only in fields
-// where the value is not nil.
-func (l *IPv6FragmentExtHdr) merge(other Layer) error {
- return mergeLayer(l, other)
-}
-
-func (l *IPv6FragmentExtHdr) String() string {
- return stringLayer(l)
-}
-
-// ICMPv6 can construct and match an ICMPv6 encapsulation.
-type ICMPv6 struct {
- LayerBase
- Type *header.ICMPv6Type
- Code *header.ICMPv6Code
- Checksum *uint16
- Ident *uint16 // Only in Echo Request/Reply.
- Pointer *uint32 // Only in Parameter Problem.
- Payload []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.ICMPv6MinimumSize+len(l.Payload))
- h := header.ICMPv6(b)
- if l.Type != nil {
- h.SetType(*l.Type)
- }
- if l.Code != nil {
- h.SetCode(*l.Code)
- }
- if n := copy(h.Payload(), l.Payload); n != len(l.Payload) {
- panic(fmt.Sprintf("copied %d bytes, expected to copy %d bytes", n, len(l.Payload)))
- }
- typ := h.Type()
- switch typ {
- case header.ICMPv6EchoRequest, header.ICMPv6EchoReply:
- if l.Ident != nil {
- h.SetIdent(*l.Ident)
- }
- case header.ICMPv6ParamProblem:
- if l.Pointer != nil {
- h.SetTypeSpecific(*l.Pointer)
- }
- }
- if l.Checksum != nil {
- h.SetChecksum(*l.Checksum)
- } else {
- // It is possible that the ICMPv6 header does not follow the IPv6 header
- // immediately, there could be one or more extension headers in between.
- // We need to search backwards to find the IPv6 header.
- for layer := l.Prev(); layer != nil; layer = layer.Prev() {
- if ipv6, ok := layer.(*IPv6); ok {
- h.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
- Header: h[:header.ICMPv6PayloadOffset],
- Src: *ipv6.SrcAddr,
- Dst: *ipv6.DstAddr,
- PayloadCsum: header.Checksum(l.Payload, 0 /* initial */),
- PayloadLen: len(l.Payload),
- }))
- break
- }
- }
- }
- 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
-}
-
-// ICMPv6Code is a helper routine that allocates a new ICMPv6Type value to store
-// v and returns a pointer to it.
-func ICMPv6Code(v header.ICMPv6Code) *header.ICMPv6Code {
- return &v
-}
-
-// parseICMPv6 parses the bytes assuming that they start with an ICMPv6 header.
-func parseICMPv6(b []byte) (Layer, layerParser) {
- h := header.ICMPv6(b)
- msgType := h.Type()
- icmpv6 := ICMPv6{
- Type: ICMPv6Type(msgType),
- Code: ICMPv6Code(h.Code()),
- Checksum: Uint16(h.Checksum()),
- Payload: h.Payload(),
- }
- switch msgType {
- case header.ICMPv6EchoRequest, header.ICMPv6EchoReply:
- icmpv6.Ident = Uint16(h.Ident())
- case header.ICMPv6ParamProblem:
- icmpv6.Pointer = Uint32(h.TypeSpecific())
- }
- return &icmpv6, nil
-}
-
-func (l *ICMPv6) match(other Layer) bool {
- return equalLayer(l, other)
-}
-
-func (l *ICMPv6) length() int {
- return header.ICMPv6MinimumSize + len(l.Payload)
-}
-
-// 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)
-}
-
-// ICMPv4 can construct and match an ICMPv4 encapsulation.
-type ICMPv4 struct {
- LayerBase
- Type *header.ICMPv4Type
- Code *header.ICMPv4Code
- Checksum *uint16
- Ident *uint16 // Only in Echo Request/Reply.
- Sequence *uint16 // Only in Echo Request/Reply.
- Pointer *uint8 // Only in Parameter Problem.
- Payload []byte
-}
-
-func (l *ICMPv4) String() string {
- return stringLayer(l)
-}
-
-// ICMPv4Type is a helper routine that allocates a new header.ICMPv4Type value
-// to store t and returns a pointer to it.
-func ICMPv4Type(t header.ICMPv4Type) *header.ICMPv4Type {
- return &t
-}
-
-// ICMPv4Code is a helper routine that allocates a new header.ICMPv4Code value
-// to store t and returns a pointer to it.
-func ICMPv4Code(t header.ICMPv4Code) *header.ICMPv4Code {
- return &t
-}
-
-// ToBytes implements Layer.ToBytes.
-func (l *ICMPv4) ToBytes() ([]byte, error) {
- b := make([]byte, header.ICMPv4MinimumSize+len(l.Payload))
- h := header.ICMPv4(b)
- if l.Type != nil {
- h.SetType(*l.Type)
- }
- if l.Code != nil {
- h.SetCode(*l.Code)
- }
- if n := copy(h.Payload(), l.Payload); n != len(l.Payload) {
- panic(fmt.Sprintf("wrong number of bytes copied into h.Payload(): got = %d, want = %d", n, len(l.Payload)))
- }
- typ := h.Type()
- switch typ {
- case header.ICMPv4EchoReply, header.ICMPv4Echo:
- if l.Ident != nil {
- h.SetIdent(*l.Ident)
- }
- if l.Sequence != nil {
- h.SetSequence(*l.Sequence)
- }
- case header.ICMPv4ParamProblem:
- if l.Pointer != nil {
- h.SetPointer(*l.Pointer)
- }
- }
-
- // The checksum must be handled last because the ICMPv4 header fields are
- // included in the computation.
- if l.Checksum != nil {
- h.SetChecksum(*l.Checksum)
- } else {
- h.SetChecksum(^header.Checksum(h, 0))
- }
-
- return h, nil
-}
-
-// parseICMPv4 parses the bytes as an ICMPv4 header, returning a Layer and a
-// parser for the encapsulated payload.
-func parseICMPv4(b []byte) (Layer, layerParser) {
- h := header.ICMPv4(b)
-
- msgType := h.Type()
- icmpv4 := ICMPv4{
- Type: ICMPv4Type(msgType),
- Code: ICMPv4Code(h.Code()),
- Checksum: Uint16(h.Checksum()),
- Payload: h.Payload(),
- }
- switch msgType {
- case header.ICMPv4EchoReply, header.ICMPv4Echo:
- icmpv4.Ident = Uint16(h.Ident())
- icmpv4.Sequence = Uint16(h.Sequence())
- case header.ICMPv4ParamProblem:
- icmpv4.Pointer = Uint8(h.Pointer())
- }
- return &icmpv4, nil
-}
-
-func (l *ICMPv4) match(other Layer) bool {
- return equalLayer(l, other)
-}
-
-func (l *ICMPv4) length() int {
- return header.ICMPv4MinimumSize + len(l.Payload)
-}
-
-// merge overrides the values in l with the values from other but only in fields
-// where the value is not nil.
-func (l *ICMPv4) 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 *header.TCPFlags
- WindowSize *uint16
- Checksum *uint16
- UrgentPointer *uint16
- Options []byte
-}
-
-func (l *TCP) String() string {
- return stringLayer(l)
-}
-
-// ToBytes implements Layer.ToBytes.
-func (l *TCP) ToBytes() ([]byte, error) {
- b := make([]byte, l.length())
- 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(uint8(*l.Flags))
- }
- if l.WindowSize != nil {
- h.SetWindowSize(*l.WindowSize)
- } else {
- h.SetWindowSize(32768)
- }
- if l.UrgentPointer != nil {
- h.SetUrgentPoiner(*l.UrgentPointer)
- }
- copy(b[header.TCPMinimumSize:], l.Options)
- header.AddTCPOptionPadding(b[header.TCPMinimumSize:], len(l.Options))
- 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
-}
-
-// payload returns a buffer.VectorisedView of l's payload.
-func payload(l Layer) (buffer.VectorisedView, error) {
- var payloadBytes buffer.VectorisedView
- for current := l.next(); current != nil; current = current.next() {
- payload, err := current.ToBytes()
- if err != nil {
- return buffer.VectorisedView{}, fmt.Errorf("can't get bytes for next header: %s", payload)
- }
- payloadBytes.AppendView(payload)
- }
- return payloadBytes, nil
-}
-
-// 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 p := l.Prev().(type) {
- case *IPv4:
- xsum = header.PseudoHeaderChecksum(protoNumber, *p.SrcAddr, *p.DstAddr, totalLength)
- case *IPv6:
- xsum = header.PseudoHeaderChecksum(protoNumber, *p.SrcAddr, *p.DstAddr, totalLength)
- default:
- // TODO(b/161246171): Support more protocols.
- return 0, fmt.Errorf("checksum for protocol %d is not supported when previous layer is %T", protoNumber, p)
- }
- payloadBytes, err := payload(l)
- if err != nil {
- return 0, err
- }
- 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: TCPFlags(h.Flags()),
- WindowSize: Uint16(h.WindowSize()),
- Checksum: Uint16(h.Checksum()),
- UrgentPointer: Uint16(h.UrgentPointer()),
- Options: b[header.TCPMinimumSize:h.DataOffset()],
- }
- return &tcp, parsePayload
-}
-
-func (l *TCP) match(other Layer) bool {
- return equalLayer(l, other)
-}
-
-func (l *TCP) length() int {
- if l.DataOffset == nil {
- // TCP header including the options must end on a 32-bit
- // boundary; the user could potentially give us a slice
- // whose length is not a multiple of 4 bytes, so we have
- // to do the alignment here.
- optlen := (len(l.Options) + 3) & ^3
- return header.TCPMinimumSize + optlen
- }
- 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 {
- return header.UDPMinimumSize
-}
-
-// 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
-}
-
-// Length returns payload byte length.
-func (l *Payload) Length() int {
- return l.length()
-}
-
-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 bc96e0c88..000000000
--- a/test/packetimpact/testbench/layers_test.go
+++ /dev/null
@@ -1,728 +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 (
- "bytes"
- "net"
- "testing"
-
- "github.com/mohae/deepcopy"
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/header"
-)
-
-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: TCPFlags(header.TCPFlagRst | header.TCPFlagAck),
- WindowSize: Uint16(64240),
- Checksum: Uint16(0x2e2b),
- },
- want: "&testbench.TCP{" +
- "SrcPort:34785 " +
- "DstPort:47767 " +
- "SeqNum:3452155723 " +
- "AckNum:2596996163 " +
- "DataOffset:5 " +
- "Flags: R A " +
- "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)
- }
- }
-}
-
-func TestTCPOptions(t *testing.T) {
- for _, tt := range []struct {
- description string
- wantBytes []byte
- wantLayers Layers
- }{
- {
- description: "without payload",
- wantBytes: []byte{
- // IPv4 Header
- 0x45, 0x00, 0x00, 0x2c, 0x00, 0x01, 0x00, 0x00, 0x40, 0x06,
- 0xf9, 0x77, 0xc0, 0xa8, 0x00, 0x02, 0xc0, 0xa8, 0x00, 0x01,
- // TCP Header
- 0x30, 0x39, 0xd4, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x60, 0x02, 0x20, 0x00, 0xf5, 0x1c, 0x00, 0x00,
- // WindowScale Option
- 0x03, 0x03, 0x02,
- // NOP Option
- 0x00,
- },
- wantLayers: []Layer{
- &IPv4{
- IHL: Uint8(20),
- TOS: Uint8(0),
- TotalLength: Uint16(44),
- ID: Uint16(1),
- Flags: Uint8(0),
- FragmentOffset: Uint16(0),
- TTL: Uint8(64),
- Protocol: Uint8(uint8(header.TCPProtocolNumber)),
- Checksum: Uint16(0xf977),
- SrcAddr: Address(tcpip.Address(net.ParseIP("192.168.0.2").To4())),
- DstAddr: Address(tcpip.Address(net.ParseIP("192.168.0.1").To4())),
- },
- &TCP{
- SrcPort: Uint16(12345),
- DstPort: Uint16(54321),
- SeqNum: Uint32(0),
- AckNum: Uint32(0),
- Flags: TCPFlags(header.TCPFlagSyn),
- WindowSize: Uint16(8192),
- Checksum: Uint16(0xf51c),
- UrgentPointer: Uint16(0),
- Options: []byte{3, 3, 2, 0},
- },
- &Payload{Bytes: nil},
- },
- },
- {
- description: "with payload",
- wantBytes: []byte{
- // IPv4 header
- 0x45, 0x00, 0x00, 0x37, 0x00, 0x01, 0x00, 0x00, 0x40, 0x06,
- 0xf9, 0x6c, 0xc0, 0xa8, 0x00, 0x02, 0xc0, 0xa8, 0x00, 0x01,
- // TCP header
- 0x30, 0x39, 0xd4, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x60, 0x02, 0x20, 0x00, 0xe5, 0x21, 0x00, 0x00,
- // WindowScale Option
- 0x03, 0x03, 0x02,
- // NOP Option
- 0x00,
- // Payload: "Sample Data"
- 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x44, 0x61, 0x74, 0x61,
- },
- wantLayers: []Layer{
- &IPv4{
- IHL: Uint8(20),
- TOS: Uint8(0),
- TotalLength: Uint16(55),
- ID: Uint16(1),
- Flags: Uint8(0),
- FragmentOffset: Uint16(0),
- TTL: Uint8(64),
- Protocol: Uint8(uint8(header.TCPProtocolNumber)),
- Checksum: Uint16(0xf96c),
- SrcAddr: Address(tcpip.Address(net.ParseIP("192.168.0.2").To4())),
- DstAddr: Address(tcpip.Address(net.ParseIP("192.168.0.1").To4())),
- },
- &TCP{
- SrcPort: Uint16(12345),
- DstPort: Uint16(54321),
- SeqNum: Uint32(0),
- AckNum: Uint32(0),
- Flags: TCPFlags(header.TCPFlagSyn),
- WindowSize: Uint16(8192),
- Checksum: Uint16(0xe521),
- UrgentPointer: Uint16(0),
- Options: []byte{3, 3, 2, 0},
- },
- &Payload{Bytes: []byte("Sample Data")},
- },
- },
- } {
- t.Run(tt.description, func(t *testing.T) {
- layers := parse(parseIPv4, tt.wantBytes)
- if !layers.match(tt.wantLayers) {
- t.Fatalf("match failed with diff: %s", layers.diff(tt.wantLayers))
- }
- gotBytes, err := layers.ToBytes()
- if err != nil {
- t.Fatalf("ToBytes() failed on %s: %s", &layers, err)
- }
- if !bytes.Equal(tt.wantBytes, gotBytes) {
- t.Fatalf("mismatching bytes, gotBytes: %x, wantBytes: %x", gotBytes, tt.wantBytes)
- }
- })
- }
-}
-
-func TestIPv6ExtHdrOptions(t *testing.T) {
- for _, tt := range []struct {
- description string
- wantBytes []byte
- wantLayers Layers
- }{
- {
- description: "IPv6/HopByHop",
- wantBytes: []byte{
- // IPv6 Header
- 0x60, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x40, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef,
- // HopByHop Options
- 0x3b, 0x00, 0x05, 0x02, 0x00, 0x00, 0x01, 0x00,
- },
- wantLayers: []Layer{
- &IPv6{
- SrcAddr: Address(tcpip.Address(net.ParseIP("::1"))),
- DstAddr: Address(tcpip.Address(net.ParseIP("fe80::dead:beef"))),
- },
- &IPv6HopByHopOptionsExtHdr{
- NextHeader: IPv6ExtHdrIdent(header.IPv6NoNextHeaderIdentifier),
- Options: []byte{0x05, 0x02, 0x00, 0x00, 0x01, 0x00},
- },
- &Payload{
- Bytes: nil,
- },
- },
- },
- {
- description: "IPv6/HopByHop/Payload",
- wantBytes: []byte{
- // IPv6 Header
- 0x60, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x40, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef,
- // HopByHop Options
- 0x3b, 0x00, 0x05, 0x02, 0x00, 0x00, 0x01, 0x00,
- // Sample Data
- 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x44, 0x61, 0x74, 0x61,
- },
- wantLayers: []Layer{
- &IPv6{
- SrcAddr: Address(tcpip.Address(net.ParseIP("::1"))),
- DstAddr: Address(tcpip.Address(net.ParseIP("fe80::dead:beef"))),
- },
- &IPv6HopByHopOptionsExtHdr{
- NextHeader: IPv6ExtHdrIdent(header.IPv6NoNextHeaderIdentifier),
- Options: []byte{0x05, 0x02, 0x00, 0x00, 0x01, 0x00},
- },
- &Payload{
- Bytes: []byte("Sample Data"),
- },
- },
- },
- {
- description: "IPv6/HopByHop/Destination/ICMPv6",
- wantBytes: []byte{
- // IPv6 Header
- 0x60, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x40, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef,
- // HopByHop Options
- 0x3c, 0x00, 0x05, 0x02, 0x00, 0x00, 0x01, 0x00,
- // Destination Options
- 0x3a, 0x00, 0x05, 0x02, 0x00, 0x00, 0x01, 0x00,
- // ICMPv6 Param Problem
- 0x04, 0x00, 0x5f, 0x98, 0x00, 0x00, 0x00, 0x06,
- },
- wantLayers: []Layer{
- &IPv6{
- SrcAddr: Address(tcpip.Address(net.ParseIP("::1"))),
- DstAddr: Address(tcpip.Address(net.ParseIP("fe80::dead:beef"))),
- },
- &IPv6HopByHopOptionsExtHdr{
- NextHeader: IPv6ExtHdrIdent(header.IPv6DestinationOptionsExtHdrIdentifier),
- Options: []byte{0x05, 0x02, 0x00, 0x00, 0x01, 0x00},
- },
- &IPv6DestinationOptionsExtHdr{
- NextHeader: IPv6ExtHdrIdent(header.IPv6ExtensionHeaderIdentifier(header.ICMPv6ProtocolNumber)),
- Options: []byte{0x05, 0x02, 0x00, 0x00, 0x01, 0x00},
- },
- &ICMPv6{
- Type: ICMPv6Type(header.ICMPv6ParamProblem),
- Code: ICMPv6Code(header.ICMPv6ErroneousHeader),
- Checksum: Uint16(0x5f98),
- Pointer: Uint32(6),
- },
- },
- },
- {
- description: "IPv6/HopByHop/Fragment",
- wantBytes: []byte{
- // IPv6 Header
- 0x60, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x40, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef,
- // HopByHop Options
- 0x2c, 0x00, 0x05, 0x02, 0x00, 0x00, 0x01, 0x00,
- // Fragment ExtHdr
- 0x3b, 0x00, 0x03, 0x20, 0x00, 0x00, 0x00, 0x2a,
- },
- wantLayers: []Layer{
- &IPv6{
- SrcAddr: Address(tcpip.Address(net.ParseIP("::1"))),
- DstAddr: Address(tcpip.Address(net.ParseIP("fe80::dead:beef"))),
- },
- &IPv6HopByHopOptionsExtHdr{
- NextHeader: IPv6ExtHdrIdent(header.IPv6FragmentExtHdrIdentifier),
- Options: []byte{0x05, 0x02, 0x00, 0x00, 0x01, 0x00},
- },
- &IPv6FragmentExtHdr{
- NextHeader: IPv6ExtHdrIdent(header.IPv6NoNextHeaderIdentifier),
- FragmentOffset: Uint16(100),
- MoreFragments: Bool(false),
- Identification: Uint32(42),
- },
- &Payload{
- Bytes: nil,
- },
- },
- },
- {
- description: "IPv6/DestOpt/Fragment/Payload",
- wantBytes: []byte{
- // IPv6 Header
- 0x60, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x3c, 0x40, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef,
- // Destination Options
- 0x2c, 0x00, 0x05, 0x02, 0x00, 0x00, 0x01, 0x00,
- // Fragment ExtHdr
- 0x3b, 0x00, 0x03, 0x21, 0x00, 0x00, 0x00, 0x2a,
- // Sample Data
- 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x44, 0x61, 0x74, 0x61,
- },
- wantLayers: []Layer{
- &IPv6{
- SrcAddr: Address(tcpip.Address(net.ParseIP("::1"))),
- DstAddr: Address(tcpip.Address(net.ParseIP("fe80::dead:beef"))),
- },
- &IPv6DestinationOptionsExtHdr{
- NextHeader: IPv6ExtHdrIdent(header.IPv6FragmentExtHdrIdentifier),
- Options: []byte{0x05, 0x02, 0x00, 0x00, 0x01, 0x00},
- },
- &IPv6FragmentExtHdr{
- NextHeader: IPv6ExtHdrIdent(header.IPv6NoNextHeaderIdentifier),
- FragmentOffset: Uint16(100),
- MoreFragments: Bool(true),
- Identification: Uint32(42),
- },
- &Payload{
- Bytes: []byte("Sample Data"),
- },
- },
- },
- {
- description: "IPv6/Fragment/Payload",
- wantBytes: []byte{
- // IPv6 Header
- 0x60, 0x00, 0x00, 0x00, 0x00, 0x13, 0x2c, 0x40, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef,
- // Fragment ExtHdr
- 0x3b, 0x00, 0x03, 0x21, 0x00, 0x00, 0x00, 0x2a,
- // Sample Data
- 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x44, 0x61, 0x74, 0x61,
- },
- wantLayers: []Layer{
- &IPv6{
- SrcAddr: Address(tcpip.Address(net.ParseIP("::1"))),
- DstAddr: Address(tcpip.Address(net.ParseIP("fe80::dead:beef"))),
- },
- &IPv6FragmentExtHdr{
- NextHeader: IPv6ExtHdrIdent(header.IPv6NoNextHeaderIdentifier),
- FragmentOffset: Uint16(100),
- MoreFragments: Bool(true),
- Identification: Uint32(42),
- },
- &Payload{
- Bytes: []byte("Sample Data"),
- },
- },
- },
- } {
- t.Run(tt.description, func(t *testing.T) {
- layers := parse(parseIPv6, tt.wantBytes)
- if !layers.match(tt.wantLayers) {
- t.Fatalf("match failed with diff: %s", layers.diff(tt.wantLayers))
- }
- // Make sure we can generate correct next header values and checksums
- for _, layer := range layers {
- switch layer := layer.(type) {
- case *IPv6HopByHopOptionsExtHdr:
- layer.NextHeader = nil
- case *IPv6DestinationOptionsExtHdr:
- layer.NextHeader = nil
- case *IPv6FragmentExtHdr:
- layer.NextHeader = nil
- case *ICMPv6:
- layer.Checksum = nil
- }
- }
- gotBytes, err := layers.ToBytes()
- if err != nil {
- t.Fatalf("ToBytes() failed on %s: %s", &layers, err)
- }
- if !bytes.Equal(tt.wantBytes, gotBytes) {
- t.Fatalf("mismatching bytes, gotBytes: %x, wantBytes: %x", gotBytes, tt.wantBytes)
- }
- })
- }
-}
diff --git a/test/packetimpact/testbench/rawsockets.go b/test/packetimpact/testbench/rawsockets.go
deleted file mode 100644
index 6d95c033d..000000000
--- a/test/packetimpact/testbench/rawsockets.go
+++ /dev/null
@@ -1,207 +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"
- "fmt"
- "net"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/hostarch"
-)
-
-// Sniffer can sniff raw packets on the wire.
-type Sniffer struct {
- fd int
-}
-
-func htons(x uint16) uint16 {
- buf := [2]byte{}
- binary.BigEndian.PutUint16(buf[:], x)
- return hostarch.ByteOrder.Uint16(buf[:])
-}
-
-// NewSniffer creates a Sniffer connected to *device.
-func (n *DUTTestNet) NewSniffer(t *testing.T) (Sniffer, error) {
- t.Helper()
-
- ifInfo, err := net.InterfaceByName(n.LocalDevName)
- if err != nil {
- return Sniffer{}, err
- }
-
- var haddr [8]byte
- copy(haddr[:], ifInfo.HardwareAddr)
- sa := unix.SockaddrLinklayer{
- Protocol: htons(unix.ETH_P_ALL),
- Ifindex: ifInfo.Index,
- }
- 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.Bind(snifferFd, &sa); 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{
- 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. If the timeout given
-// is 0, then no read attempt will be made.
-func (s *Sniffer) Recv(t *testing.T, timeout time.Duration) []byte {
- t.Helper()
-
- deadline := time.Now().Add(timeout)
- for {
- timeout = time.Until(deadline)
- if timeout <= 0 {
- return nil
- }
- usec := timeout.Microseconds()
- if usec == 0 {
- // Timeout is less than a microsecond; set usec to 1 to avoid
- // blocking indefinitely.
- usec = 1
- }
- const microsInOne = 1e6
- tv := unix.Timeval{
- Sec: usec / microsInOne,
- Usec: usec % microsInOne,
- }
- if err := unix.SetsockoptTimeval(s.fd, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &tv); err != nil {
- 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 {
- t.Fatalf("can't read: %s", err)
- }
- if nread > maxReadSize {
- t.Fatalf("received a truncated frame of %d bytes, want at most %d bytes", nread, maxReadSize)
- }
- return buf[:nread]
- }
-}
-
-// Drain drains the Sniffer's socket receive buffer by receiving until there's
-// nothing else to receive.
-func (s *Sniffer) Drain(t *testing.T) {
- t.Helper()
-
- flags, err := unix.FcntlInt(uintptr(s.fd), unix.F_GETFL, 0)
- if err != nil {
- t.Fatalf("failed to get sniffer socket fd flags: %s", err)
- }
- nonBlockingFlags := flags | unix.O_NONBLOCK
- if _, err := unix.FcntlInt(uintptr(s.fd), unix.F_SETFL, nonBlockingFlags); err != nil {
- t.Fatalf("failed to make sniffer socket non-blocking with flags %b: %s", nonBlockingFlags, 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 {
- t.Fatalf("failed to restore sniffer socket fd flags to %b: %s", flags, 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 {
- fd int
-}
-
-// NewInjector creates a new injector on *device.
-func (n *DUTTestNet) NewInjector(t *testing.T) (Injector, error) {
- t.Helper()
-
- ifInfo, err := net.InterfaceByName(n.LocalDevName)
- if err != nil {
- return Injector{}, err
- }
-
- var haddr [8]byte
- copy(haddr[:], ifInfo.HardwareAddr)
- sa := unix.SockaddrLinklayer{
- Protocol: htons(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{
- fd: injectFd,
- }, nil
-}
-
-// Send a raw frame.
-func (i *Injector) Send(t *testing.T, b []byte) {
- t.Helper()
-
- n, err := unix.Write(i.fd, b)
- if err != nil {
- t.Fatalf("can't write bytes of len %d: %s", len(b), err)
- }
- if n != len(b) {
- t.Fatalf("got %d bytes written, want %d", n, len(b))
- }
-}
-
-// 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/testbench/testbench.go b/test/packetimpact/testbench/testbench.go
deleted file mode 100644
index 38ae9c1d7..000000000
--- a/test/packetimpact/testbench/testbench.go
+++ /dev/null
@@ -1,180 +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. It is the packetimpact test API.
-package testbench
-
-import (
- "encoding/json"
- "flag"
- "fmt"
- "math/rand"
- "net"
- "testing"
- "time"
-)
-
-var (
- // Native indicates that the test is being run natively.
- Native = false
- // RPCKeepalive is the gRPC keepalive.
- RPCKeepalive = 10 * time.Second
-
- // dutInfosJSON is the json string that describes information about all the
- // duts available to use.
- dutInfosJSON string
- // dutInfo is the pool among which the testbench can choose a DUT to work
- // with.
- dutInfo chan *DUTInfo
-)
-
-// DUTInfo has both network and uname information about the DUT.
-type DUTInfo struct {
- Uname *DUTUname
- Net *DUTTestNet
-}
-
-// DUTUname contains information about the DUT from uname.
-type DUTUname struct {
- Machine string
- KernelName string
- KernelRelease string
- KernelVersion string
- OperatingSystem string
-}
-
-// IsLinux returns true if the DUT is running Linux.
-func (n *DUTUname) IsLinux() bool {
- return Native && n.OperatingSystem == "GNU/Linux"
-}
-
-// IsGvisor returns true if the DUT is running gVisor.
-func (*DUTUname) IsGvisor() bool {
- return !Native
-}
-
-// IsFuchsia returns true if the DUT is running Fuchsia.
-func (n *DUTUname) IsFuchsia() bool {
- return Native && n.OperatingSystem == "Fuchsia"
-}
-
-// DUTTestNet describes the test network setup on dut and how the testbench
-// should connect with an existing DUT.
-type DUTTestNet struct {
- // LocalMAC is the local MAC address on the test network.
- LocalMAC net.HardwareAddr
- // RemoteMAC is the DUT's MAC address on the test network.
- RemoteMAC net.HardwareAddr
- // LocalIPv4 is the local IPv4 address on the test network.
- LocalIPv4 net.IP
- // RemoteIPv4 is the DUT's IPv4 address on the test network.
- RemoteIPv4 net.IP
- // IPv4PrefixLength is the network prefix length of the IPv4 test network.
- IPv4PrefixLength int
- // LocalIPv6 is the local IPv6 address on the test network.
- LocalIPv6 net.IP
- // RemoteIPv6 is the DUT's IPv6 address on the test network.
- RemoteIPv6 net.IP
- // LocalDevID is the ID of the local interface on the test network.
- LocalDevID uint32
- // RemoteDevID is the ID of the remote interface on the test network.
- RemoteDevID uint32
- // LocalDevName is the device that testbench uses to inject traffic.
- LocalDevName string
- // RemoteDevName is the device name on the DUT, individual tests can
- // use the name to construct tests.
- RemoteDevName string
-
- // The following two fields on actually on the control network instead
- // of the test network, including them for convenience.
-
- // POSIXServerIP is the POSIX server's IP address on the control network.
- POSIXServerIP net.IP
- // POSIXServerPort is the UDP port the POSIX server is bound to on the
- // control network.
- POSIXServerPort uint16
-}
-
-// SubnetBroadcast returns the test network's subnet broadcast address.
-func (n *DUTTestNet) SubnetBroadcast() net.IP {
- addr := append([]byte(nil), n.RemoteIPv4...)
- mask := net.CIDRMask(n.IPv4PrefixLength, net.IPv4len*8)
- for i := range addr {
- addr[i] |= ^mask[i]
- }
- return addr
-}
-
-// registerFlags defines flags and associates them with the package-level
-// exported variables above. It should be called by tests in their init
-// functions.
-func registerFlags(fs *flag.FlagSet) {
- fs.BoolVar(&Native, "native", Native, "whether the test is running natively")
- fs.DurationVar(&RPCKeepalive, "rpc_keepalive", RPCKeepalive, "gRPC keepalive")
- fs.StringVar(&dutInfosJSON, "dut_infos_json", dutInfosJSON, "json that describes the DUTs")
-}
-
-// Initialize initializes the testbench, it parse the flags and sets up the
-// pool of test networks for testbench's later use.
-func Initialize(fs *flag.FlagSet) {
- testing.Init()
- registerFlags(fs)
- flag.Parse()
- if err := loadDUTInfos(); err != nil {
- panic(err)
- }
-}
-
-// loadDUTInfos loads available DUT test infos from the json file, it
-// must be called after flag.Parse().
-func loadDUTInfos() error {
- var dutInfos []DUTInfo
- if err := json.Unmarshal([]byte(dutInfosJSON), &dutInfos); err != nil {
- return fmt.Errorf("failed to unmarshal JSON: %w", err)
- }
- if got, want := len(dutInfos), 1; got < want {
- return fmt.Errorf("got %d DUTs, the test requires at least %d DUTs", got, want)
- }
- // Using a buffered channel as semaphore
- dutInfo = make(chan *DUTInfo, len(dutInfos))
- for i := range dutInfos {
- dutInfos[i].Net.LocalIPv4 = dutInfos[i].Net.LocalIPv4.To4()
- dutInfos[i].Net.RemoteIPv4 = dutInfos[i].Net.RemoteIPv4.To4()
- dutInfo <- &dutInfos[i]
- }
- return nil
-}
-
-// GenerateRandomPayload generates a random byte slice of the specified length,
-// causing a fatal test failure if it is unable to do so.
-func GenerateRandomPayload(t *testing.T, n int) []byte {
- t.Helper()
- buf := make([]byte, n)
- if _, err := rand.Read(buf); err != nil {
- t.Fatalf("rand.Read(buf) failed: %s", err)
- }
- return buf
-}
-
-// getDUTInfo returns information about an available DUT from the pool. If no
-// DUT is readily available, getDUTInfo blocks until one becomes available.
-func getDUTInfo() *DUTInfo {
- return <-dutInfo
-}
-
-// release returns the DUTInfo back to the pool.
-func (info *DUTInfo) release() {
- dutInfo <- info
-}
diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD
deleted file mode 100644
index 4cff0cf4c..000000000
--- a/test/packetimpact/tests/BUILD
+++ /dev/null
@@ -1,431 +0,0 @@
-load("//test/packetimpact/runner:defs.bzl", "ALL_TESTS", "packetimpact_go_test", "packetimpact_testbench", "validate_all_tests")
-
-package(
- default_visibility = ["//test/packetimpact:__subpackages__"],
- licenses = ["notice"],
-)
-
-packetimpact_testbench(
- 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_testbench(
- name = "ipv4_id_uniqueness",
- srcs = ["ipv4_id_uniqueness_test.go"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- name = "udp_discard_mcast_source_addr",
- srcs = ["udp_discard_mcast_source_addr_test.go"],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- name = "udp_any_addr_recv_unicast",
- srcs = ["udp_any_addr_recv_unicast_test.go"],
- deps = [
- "//pkg/tcpip",
- "//test/packetimpact/testbench",
- "@com_github_google_go_cmp//cmp:go_default_library",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- name = "udp_icmp_error_propagation",
- srcs = ["udp_icmp_error_propagation_test.go"],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- name = "tcp_window_shrink",
- srcs = ["tcp_window_shrink_test.go"],
- deps = [
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- name = "tcp_zero_window_probe",
- srcs = ["tcp_zero_window_probe_test.go"],
- deps = [
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- name = "tcp_zero_window_probe_retransmit",
- srcs = ["tcp_zero_window_probe_retransmit_test.go"],
- deps = [
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- name = "tcp_zero_window_probe_usertimeout",
- srcs = ["tcp_zero_window_probe_usertimeout_test.go"],
- deps = [
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- name = "tcp_retransmits",
- srcs = ["tcp_retransmits_test.go"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- 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_testbench(
- 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_testbench(
- name = "tcp_send_window_sizes_piggyback",
- srcs = ["tcp_send_window_sizes_piggyback_test.go"],
- deps = [
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- name = "tcp_unacc_seq_ack",
- srcs = ["tcp_unacc_seq_ack_test.go"],
- deps = [
- "//pkg/tcpip/header",
- "//pkg/tcpip/seqnum",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- name = "tcp_paws_mechanism",
- srcs = ["tcp_paws_mechanism_test.go"],
- deps = [
- "//pkg/tcpip/header",
- "//pkg/tcpip/seqnum",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- 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_testbench(
- name = "tcp_queue_send_recv_in_syn_sent",
- srcs = ["tcp_queue_send_recv_in_syn_sent_test.go"],
- deps = [
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- name = "tcp_synsent_reset",
- srcs = ["tcp_synsent_reset_test.go"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- name = "tcp_synrcvd_reset",
- srcs = ["tcp_synrcvd_reset_test.go"],
- deps = [
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- name = "tcp_network_unreachable",
- srcs = ["tcp_network_unreachable_test.go"],
- deps = [
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- name = "tcp_cork_mss",
- srcs = ["tcp_cork_mss_test.go"],
- deps = [
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- name = "tcp_handshake_window_size",
- srcs = ["tcp_handshake_window_size_test.go"],
- deps = [
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- name = "tcp_timewait_reset",
- srcs = ["tcp_timewait_reset_test.go"],
- deps = [
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- name = "icmpv6_param_problem",
- srcs = ["icmpv6_param_problem_test.go"],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- name = "ipv6_unknown_options_action",
- srcs = ["ipv6_unknown_options_action_test.go"],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- name = "ipv4_fragment_reassembly",
- srcs = ["ipv4_fragment_reassembly_test.go"],
- deps = [
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@com_github_google_go_cmp//cmp:go_default_library",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- name = "ipv6_fragment_reassembly",
- srcs = ["ipv6_fragment_reassembly_test.go"],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@com_github_google_go_cmp//cmp:go_default_library",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- name = "ipv6_fragment_icmp_error",
- srcs = ["ipv6_fragment_icmp_error_test.go"],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/header",
- "//pkg/tcpip/network/ipv6",
- "//test/packetimpact/testbench",
- "@com_github_google_go_cmp//cmp:go_default_library",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- name = "tcp_linger",
- srcs = ["tcp_linger_test.go"],
- deps = [
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- name = "tcp_rcv_buf_space",
- srcs = ["tcp_rcv_buf_space_test.go"],
- deps = [
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- name = "tcp_zero_receive_window",
- srcs = ["tcp_zero_receive_window_test.go"],
- deps = [
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- name = "tcp_rack",
- srcs = ["tcp_rack_test.go"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/tcpip/header",
- "//pkg/tcpip/seqnum",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- name = "tcp_info",
- srcs = ["tcp_info_test.go"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- name = "tcp_fin_retransmission",
- srcs = ["tcp_fin_retransmission_test.go"],
- deps = [
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- name = "tcp_listen_backlog",
- srcs = ["tcp_listen_backlog_test.go"],
- deps = [
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- name = "tcp_syncookie",
- srcs = ["tcp_syncookie_test.go"],
- deps = [
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@com_github_google_go_cmp//cmp:go_default_library",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- name = "tcp_connect_icmp_error",
- srcs = ["tcp_connect_icmp_error_test.go"],
- deps = [
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
- name = "generic_dgram_socket_send_recv",
- srcs = ["generic_dgram_socket_send_recv_test.go"],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@com_github_google_go_cmp//cmp:go_default_library",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-validate_all_tests()
-
-[packetimpact_go_test(
- name = t.name,
- timeout = t.timeout if hasattr(t, "timeout") else "moderate",
- expect_netstack_failure = hasattr(t, "expect_netstack_failure"),
- num_duts = t.num_duts if hasattr(t, "num_duts") else 1,
-) for t in ALL_TESTS]
-
-test_suite(
- name = "all_tests",
- tags = [
- "local",
- "manual",
- "packetimpact",
- ],
- tests = existing_rules(),
-)
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 cff8ca51d..000000000
--- a/test/packetimpact/tests/fin_wait2_timeout_test.go
+++ /dev/null
@@ -1,74 +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 (
- "flag"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-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 := testbench.NewDUT(t)
- listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(t, listenFd)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- defer conn.Close(t)
- conn.Connect(t)
-
- acceptFd, _ := dut.Accept(t, listenFd)
- if tt.linger2 {
- tv := unix.Timeval{Sec: 1, Usec: 0}
- dut.SetSockOptTimeval(t, acceptFd, unix.SOL_TCP, unix.TCP_LINGER2, &tv)
- }
- dut.Close(t, acceptFd)
-
- if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil {
- t.Fatalf("expected a FIN-ACK within 1 second but got none: %s", err)
- }
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
-
- time.Sleep(5 * time.Second)
- conn.Drain(t)
-
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
- if tt.linger2 {
- if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(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(t, testbench.TCP{Flags: testbench.TCPFlags(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/generic_dgram_socket_send_recv_test.go b/test/packetimpact/tests/generic_dgram_socket_send_recv_test.go
deleted file mode 100644
index a9ffafc74..000000000
--- a/test/packetimpact/tests/generic_dgram_socket_send_recv_test.go
+++ /dev/null
@@ -1,781 +0,0 @@
-// Copyright 2021 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 generic_dgram_socket_send_recv_test
-
-import (
- "context"
- "flag"
- "fmt"
- "net"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-const (
- // Even though sockets allow larger datagrams we don't test it here as they
- // need to be fragmented and written out as individual frames.
-
- maxICMPv4PayloadSize = header.IPv4MinimumMTU - header.EthernetMinimumSize - header.IPv4MinimumSize - header.ICMPv4MinimumSize
- maxICMPv6PayloadSize = header.IPv6MinimumMTU - header.EthernetMinimumSize - header.IPv6MinimumSize - header.ICMPv6MinimumSize
- maxUDPv4PayloadSize = header.IPv4MinimumMTU - header.EthernetMinimumSize - header.IPv4MinimumSize - header.UDPMinimumSize
- maxUDPv6PayloadSize = header.IPv6MinimumMTU - header.EthernetMinimumSize - header.IPv6MinimumSize - header.UDPMinimumSize
-)
-
-func maxUDPPayloadSize(addr net.IP) int {
- if addr.To4() != nil {
- return maxUDPv4PayloadSize
- }
- return maxUDPv6PayloadSize
-}
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-func expectedEthLayer(t *testing.T, dut testbench.DUT, socketFD int32, sendTo net.IP) testbench.Layer {
- t.Helper()
- dst := func() tcpip.LinkAddress {
- if isBroadcast(dut, sendTo) {
- dut.SetSockOptInt(t, socketFD, unix.SOL_SOCKET, unix.SO_BROADCAST, 1)
-
- // When sending to broadcast (subnet or limited), the expected ethernet
- // address is also broadcast.
- return header.EthernetBroadcastAddress
- }
- if sendTo.IsMulticast() {
- if sendTo4 := sendTo.To4(); sendTo4 != nil {
- return header.EthernetAddressFromMulticastIPv4Address(tcpip.Address(sendTo4))
- }
- return header.EthernetAddressFromMulticastIPv6Address(tcpip.Address(sendTo.To16()))
- }
- return ""
- }()
- var ether testbench.Ether
- if len(dst) != 0 {
- ether.DstAddr = &dst
- }
- return &ether
-}
-
-type protocolTest interface {
- Name() string
- Send(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool)
- Receive(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool)
-}
-
-func TestSocket(t *testing.T) {
- dut := testbench.NewDUT(t)
- subnetBroadcast := dut.Net.SubnetBroadcast()
-
- for _, proto := range []protocolTest{
- &icmpV4Test{},
- &icmpV6Test{},
- &udpTest{},
- } {
- t.Run(proto.Name(), func(t *testing.T) {
- // Test every combination of bound/unbound, broadcast/multicast/unicast
- // bound/destination address, and bound/not-bound to device.
- for _, bindTo := range []net.IP{
- nil, // Do not bind.
- net.IPv4zero,
- net.IPv4bcast,
- net.IPv4allsys,
- net.IPv6zero,
- subnetBroadcast,
- dut.Net.RemoteIPv4,
- dut.Net.RemoteIPv6,
- } {
- t.Run(fmt.Sprintf("bindTo=%s", bindTo), func(t *testing.T) {
- for _, sendTo := range []net.IP{
- net.IPv4bcast,
- net.IPv4allsys,
- subnetBroadcast,
- dut.Net.LocalIPv4,
- dut.Net.LocalIPv6,
- dut.Net.RemoteIPv4,
- dut.Net.RemoteIPv6,
- } {
- t.Run(fmt.Sprintf("sendTo=%s", sendTo), func(t *testing.T) {
- for _, bindToDevice := range []bool{true, false} {
- t.Run(fmt.Sprintf("bindToDevice=%t", bindToDevice), func(t *testing.T) {
- t.Run("Send", func(t *testing.T) {
- proto.Send(t, dut, bindTo, sendTo, bindToDevice)
- })
- t.Run("Receive", func(t *testing.T) {
- proto.Receive(t, dut, bindTo, sendTo, bindToDevice)
- })
- })
- }
- })
- }
- })
- }
- })
- }
-}
-
-type icmpV4TestEnv struct {
- socketFD int32
- ident uint16
- conn testbench.IPv4Conn
- layers testbench.Layers
-}
-
-type icmpV4Test struct{}
-
-func (test *icmpV4Test) setup(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) icmpV4TestEnv {
- t.Helper()
-
- // Tell the DUT to create a socket.
- var socketFD int32
- var ident uint16
-
- if bindTo != nil {
- socketFD, ident = dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_ICMP, bindTo)
- } else {
- // An unbound socket will auto-bind to INADDR_ANY.
- socketFD = dut.Socket(t, unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_ICMP)
- }
- t.Cleanup(func() {
- dut.Close(t, socketFD)
- })
-
- if bindToDevice {
- dut.SetSockOpt(t, socketFD, unix.SOL_SOCKET, unix.SO_BINDTODEVICE, []byte(dut.Net.RemoteDevName))
- }
-
- // Create a socket on the test runner.
- conn := dut.Net.NewIPv4Conn(t, testbench.IPv4{}, testbench.IPv4{})
- t.Cleanup(func() {
- conn.Close(t)
- })
-
- return icmpV4TestEnv{
- socketFD: socketFD,
- ident: ident,
- conn: conn,
- layers: testbench.Layers{
- expectedEthLayer(t, dut, socketFD, sendTo),
- &testbench.IPv4{
- DstAddr: testbench.Address(tcpip.Address(sendTo.To4())),
- },
- },
- }
-}
-
-var _ protocolTest = (*icmpV4Test)(nil)
-
-func (*icmpV4Test) Name() string { return "icmpv4" }
-
-func (test *icmpV4Test) Send(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) {
- if bindTo.To4() == nil || isBroadcastOrMulticast(dut, bindTo) {
- // ICMPv4 sockets cannot bind to IPv6, broadcast, or multicast
- // addresses.
- return
- }
-
- isV4 := sendTo.To4() != nil
-
- // TODO(gvisor.dev/issue/5681): Remove this case once ICMP sockets allow
- // sending to broadcast and multicast addresses.
- if (dut.Uname.IsGvisor() || dut.Uname.IsFuchsia()) && isV4 && isBroadcastOrMulticast(dut, sendTo) {
- // expectPacket cannot be false. In some cases the packet will send, but
- // with IPv4 destination incorrectly set to RemoteIPv4. It's all bad and
- // not worth the effort to create a special case when this occurs.
- t.Skip("TODO(gvisor.dev/issue/5681): Allow sending to broadcast and multicast addresses with ICMP sockets.")
- }
-
- expectPacket := isV4 && !sendTo.Equal(dut.Net.RemoteIPv4)
- switch {
- case bindTo.Equal(dut.Net.RemoteIPv4):
- // If we're explicitly bound to an interface's unicast address,
- // packets are always sent on that interface.
- case bindToDevice:
- // If we're explicitly bound to an interface, packets are always
- // sent on that interface.
- case !sendTo.Equal(net.IPv4bcast) && !sendTo.IsMulticast():
- // If we're not sending to limited broadcast or multicast, the route
- // table will be consulted and packets will be sent on the correct
- // interface.
- default:
- expectPacket = false
- }
-
- env := test.setup(t, dut, bindTo, sendTo, bindToDevice)
-
- for name, payload := range map[string][]byte{
- "empty": nil,
- "small": []byte("hello world"),
- "random1k": testbench.GenerateRandomPayload(t, maxICMPv4PayloadSize),
- } {
- t.Run(name, func(t *testing.T) {
- icmpLayer := &testbench.ICMPv4{
- Type: testbench.ICMPv4Type(header.ICMPv4Echo),
- Payload: payload,
- }
- bytes, err := icmpLayer.ToBytes()
- if err != nil {
- t.Fatalf("icmpLayer.ToBytes() = %s", err)
- }
- destSockaddr := unix.SockaddrInet4{}
- copy(destSockaddr.Addr[:], sendTo.To4())
-
- // Tell the DUT to send a packet out the ICMP socket.
- if got, want := dut.SendTo(t, env.socketFD, bytes, 0, &destSockaddr), len(bytes); int(got) != want {
- t.Fatalf("got dut.SendTo = %d, want %d", got, want)
- }
-
- // Verify the test runner received an ICMP packet with the correctly
- // set "ident".
- if env.ident != 0 {
- icmpLayer.Ident = &env.ident
- }
- want := append(env.layers, icmpLayer)
- if got, ok := env.conn.ListenForFrame(t, want, time.Second); !ok && expectPacket {
- t.Fatalf("did not receive expected frame matching %s\nGot frames: %s", want, got)
- } else if ok && !expectPacket {
- matchedFrame := got[len(got)-1]
- t.Fatalf("got unexpected frame matching %s\nGot frame: %s", want, matchedFrame)
- }
- })
- }
-}
-
-func (test *icmpV4Test) Receive(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) {
- if bindTo.To4() == nil || isBroadcastOrMulticast(dut, bindTo) {
- // ICMPv4 sockets cannot bind to IPv6, broadcast, or multicast
- // addresses.
- return
- }
-
- expectPacket := (bindTo.Equal(dut.Net.RemoteIPv4) || bindTo.Equal(net.IPv4zero)) && sendTo.Equal(dut.Net.RemoteIPv4)
-
- // TODO(gvisor.dev/issue/5763): Remove this if statement once gVisor
- // restricts ICMP sockets to receive only from unicast addresses.
- if (dut.Uname.IsGvisor() || dut.Uname.IsFuchsia()) && bindTo.Equal(net.IPv4zero) && isBroadcastOrMulticast(dut, sendTo) {
- expectPacket = true
- }
-
- env := test.setup(t, dut, bindTo, sendTo, bindToDevice)
-
- for name, payload := range map[string][]byte{
- "empty": nil,
- "small": []byte("hello world"),
- "random1k": testbench.GenerateRandomPayload(t, maxICMPv4PayloadSize),
- } {
- t.Run(name, func(t *testing.T) {
- icmpLayer := &testbench.ICMPv4{
- Type: testbench.ICMPv4Type(header.ICMPv4EchoReply),
- Payload: payload,
- }
- if env.ident != 0 {
- icmpLayer.Ident = &env.ident
- }
-
- // Send an ICMPv4 packet from the test runner to the DUT.
- frame := env.conn.CreateFrame(t, env.layers, icmpLayer)
- env.conn.SendFrame(t, frame)
-
- // Verify the behavior of the ICMP socket on the DUT.
- if expectPacket {
- payload, err := icmpLayer.ToBytes()
- if err != nil {
- t.Fatalf("icmpLayer.ToBytes() = %s", err)
- }
-
- // Receive one extra byte to assert the length of the
- // packet received in the case where the packet contains
- // more data than expected.
- len := int32(len(payload)) + 1
- got, want := dut.Recv(t, env.socketFD, len, 0), payload
- if diff := cmp.Diff(want, got); diff != "" {
- t.Errorf("received payload does not match sent payload, diff (-want, +got):\n%s", diff)
- }
- } else {
- // Expected receive error, set a short receive timeout.
- dut.SetSockOptTimeval(
- t,
- env.socketFD,
- unix.SOL_SOCKET,
- unix.SO_RCVTIMEO,
- &unix.Timeval{
- Sec: 1,
- Usec: 0,
- },
- )
- ret, recvPayload, errno := dut.RecvWithErrno(context.Background(), t, env.socketFD, maxICMPv4PayloadSize, 0)
- if errno != unix.EAGAIN || errno != unix.EWOULDBLOCK {
- t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, recvPayload, errno)
- }
- }
- })
- }
-}
-
-type icmpV6TestEnv struct {
- socketFD int32
- ident uint16
- conn testbench.IPv6Conn
- layers testbench.Layers
-}
-
-// icmpV6Test and icmpV4Test look substantially similar at first look, but have
-// enough subtle differences in setup and test expectations to discourage
-// refactoring:
-// - Different IP layers
-// - Different testbench.Connections
-// - Different UNIX domain and proto arguments
-// - Different expectPacket and wantErrno for send and receive
-type icmpV6Test struct{}
-
-func (test *icmpV6Test) setup(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) icmpV6TestEnv {
- t.Helper()
-
- // Tell the DUT to create a socket.
- var socketFD int32
- var ident uint16
-
- if bindTo != nil {
- socketFD, ident = dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_ICMPV6, bindTo)
- } else {
- // An unbound socket will auto-bind to IN6ADDR_ANY_INIT.
- socketFD = dut.Socket(t, unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_ICMPV6)
- }
- t.Cleanup(func() {
- dut.Close(t, socketFD)
- })
-
- if bindToDevice {
- dut.SetSockOpt(t, socketFD, unix.SOL_SOCKET, unix.SO_BINDTODEVICE, []byte(dut.Net.RemoteDevName))
- }
-
- // Create a socket on the test runner.
- conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{})
- t.Cleanup(func() {
- conn.Close(t)
- })
-
- return icmpV6TestEnv{
- socketFD: socketFD,
- ident: ident,
- conn: conn,
- layers: testbench.Layers{
- expectedEthLayer(t, dut, socketFD, sendTo),
- &testbench.IPv6{
- DstAddr: testbench.Address(tcpip.Address(sendTo.To16())),
- },
- },
- }
-}
-
-var _ protocolTest = (*icmpV6Test)(nil)
-
-func (*icmpV6Test) Name() string { return "icmpv6" }
-
-func (test *icmpV6Test) Send(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) {
- if bindTo.To4() != nil || bindTo.IsMulticast() {
- // ICMPv6 sockets cannot bind to IPv4 or multicast addresses.
- return
- }
-
- expectPacket := sendTo.Equal(dut.Net.LocalIPv6)
- wantErrno := unix.Errno(0)
-
- if sendTo.To4() != nil {
- wantErrno = unix.EINVAL
- }
-
- // TODO(gvisor.dev/issue/5966): Remove this if statement once ICMPv6 sockets
- // return EINVAL after calling sendto with an IPv4 address.
- if (dut.Uname.IsGvisor() || dut.Uname.IsFuchsia()) && sendTo.To4() != nil {
- switch {
- case bindTo.Equal(dut.Net.RemoteIPv6):
- wantErrno = unix.ENETUNREACH
- case bindTo.Equal(net.IPv6zero) || bindTo == nil:
- wantErrno = unix.Errno(0)
- }
- }
-
- env := test.setup(t, dut, bindTo, sendTo, bindToDevice)
-
- for name, payload := range map[string][]byte{
- "empty": nil,
- "small": []byte("hello world"),
- "random1k": testbench.GenerateRandomPayload(t, maxICMPv6PayloadSize),
- } {
- t.Run(name, func(t *testing.T) {
- icmpLayer := &testbench.ICMPv6{
- Type: testbench.ICMPv6Type(header.ICMPv6EchoRequest),
- Payload: payload,
- }
- bytes, err := icmpLayer.ToBytes()
- if err != nil {
- t.Fatalf("icmpLayer.ToBytes() = %s", err)
- }
- destSockaddr := unix.SockaddrInet6{
- ZoneId: dut.Net.RemoteDevID,
- }
- copy(destSockaddr.Addr[:], sendTo.To16())
-
- // Tell the DUT to send a packet out the ICMPv6 socket.
- gotRet, gotErrno := dut.SendToWithErrno(context.Background(), t, env.socketFD, bytes, 0, &destSockaddr)
-
- if gotErrno != wantErrno {
- t.Fatalf("got dut.SendToWithErrno(_, _, %d, _, _, %s) = (_, %s), want = (_, %s)", env.socketFD, sendTo, gotErrno, wantErrno)
- }
- if wantErrno != 0 {
- return
- }
- if got, want := int(gotRet), len(bytes); got != want {
- t.Fatalf("got dut.SendToWithErrno(_, _, %d, _, _, %s) = (%d, _), want = (%d, _)", env.socketFD, sendTo, got, want)
- }
-
- // Verify the test runner received an ICMPv6 packet with the
- // correctly set "ident".
- if env.ident != 0 {
- icmpLayer.Ident = &env.ident
- }
- want := append(env.layers, icmpLayer)
- if got, ok := env.conn.ListenForFrame(t, want, time.Second); !ok && expectPacket {
- t.Fatalf("did not receive expected frame matching %s\nGot frames: %s", want, got)
- } else if ok && !expectPacket {
- matchedFrame := got[len(got)-1]
- t.Fatalf("got unexpected frame matching %s\nGot frame: %s", want, matchedFrame)
- }
- })
- }
-}
-
-func (test *icmpV6Test) Receive(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) {
- if bindTo.To4() != nil || bindTo.IsMulticast() {
- // ICMPv6 sockets cannot bind to IPv4 or multicast addresses.
- return
- }
-
- expectPacket := true
- switch {
- case bindTo.Equal(dut.Net.RemoteIPv6) && sendTo.Equal(dut.Net.RemoteIPv6):
- case bindTo.Equal(net.IPv6zero) && sendTo.Equal(dut.Net.RemoteIPv6):
- case bindTo.Equal(net.IPv6zero) && sendTo.Equal(net.IPv6linklocalallnodes):
- default:
- expectPacket = false
- }
-
- // TODO(gvisor.dev/issue/5763): Remove this if statement once gVisor
- // restricts ICMP sockets to receive only from unicast addresses.
- if (dut.Uname.IsGvisor() || dut.Uname.IsFuchsia()) && bindTo.Equal(net.IPv6zero) && isBroadcastOrMulticast(dut, sendTo) {
- expectPacket = false
- }
-
- env := test.setup(t, dut, bindTo, sendTo, bindToDevice)
-
- for name, payload := range map[string][]byte{
- "empty": nil,
- "small": []byte("hello world"),
- "random1k": testbench.GenerateRandomPayload(t, maxICMPv6PayloadSize),
- } {
- t.Run(name, func(t *testing.T) {
- icmpLayer := &testbench.ICMPv6{
- Type: testbench.ICMPv6Type(header.ICMPv6EchoReply),
- Payload: payload,
- }
- if env.ident != 0 {
- icmpLayer.Ident = &env.ident
- }
-
- // Send an ICMPv6 packet from the test runner to the DUT.
- frame := env.conn.CreateFrame(t, env.layers, icmpLayer)
- env.conn.SendFrame(t, frame)
-
- // Verify the behavior of the ICMPv6 socket on the DUT.
- if expectPacket {
- payload, err := icmpLayer.ToBytes()
- if err != nil {
- t.Fatalf("icmpLayer.ToBytes() = %s", err)
- }
-
- // Receive one extra byte to assert the length of the
- // packet received in the case where the packet contains
- // more data than expected.
- len := int32(len(payload)) + 1
- got, want := dut.Recv(t, env.socketFD, len, 0), payload
- if diff := cmp.Diff(want, got); diff != "" {
- t.Errorf("received payload does not match sent payload, diff (-want, +got):\n%s", diff)
- }
- } else {
- // Expected receive error, set a short receive timeout.
- dut.SetSockOptTimeval(
- t,
- env.socketFD,
- unix.SOL_SOCKET,
- unix.SO_RCVTIMEO,
- &unix.Timeval{
- Sec: 1,
- Usec: 0,
- },
- )
- ret, recvPayload, errno := dut.RecvWithErrno(context.Background(), t, env.socketFD, maxICMPv6PayloadSize, 0)
- if errno != unix.EAGAIN || errno != unix.EWOULDBLOCK {
- t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, recvPayload, errno)
- }
- }
- })
- }
-}
-
-type udpConn interface {
- SrcPort(*testing.T) uint16
- SendFrame(*testing.T, testbench.Layers, ...testbench.Layer)
- ListenForFrame(*testing.T, testbench.Layers, time.Duration) ([]testbench.Layers, bool)
- Close(*testing.T)
-}
-
-type udpTestEnv struct {
- socketFD int32
- conn udpConn
- layers testbench.Layers
-}
-
-type udpTest struct{}
-
-func (test *udpTest) setup(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) udpTestEnv {
- t.Helper()
-
- var (
- socketFD int32
- outgoingUDP, incomingUDP testbench.UDP
- )
-
- // Tell the DUT to create a socket.
- if bindTo != nil {
- var remotePort uint16
- socketFD, remotePort = dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, bindTo)
- outgoingUDP.DstPort = &remotePort
- incomingUDP.SrcPort = &remotePort
- } else {
- // An unbound socket will auto-bind to INADDR_ANY.
- socketFD = dut.Socket(t, unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_UDP)
- }
- t.Cleanup(func() {
- dut.Close(t, socketFD)
- })
-
- if bindToDevice {
- dut.SetSockOpt(t, socketFD, unix.SOL_SOCKET, unix.SO_BINDTODEVICE, []byte(dut.Net.RemoteDevName))
- }
-
- // Create a socket on the test runner.
- var conn udpConn
- var ipLayer testbench.Layer
- if addr := sendTo.To4(); addr != nil {
- udpConn := dut.Net.NewUDPIPv4(t, outgoingUDP, incomingUDP)
- conn = &udpConn
- ipLayer = &testbench.IPv4{
- DstAddr: testbench.Address(tcpip.Address(addr)),
- }
- } else {
- udpConn := dut.Net.NewUDPIPv6(t, outgoingUDP, incomingUDP)
- conn = &udpConn
- ipLayer = &testbench.IPv6{
- DstAddr: testbench.Address(tcpip.Address(sendTo.To16())),
- }
- }
- t.Cleanup(func() {
- conn.Close(t)
- })
-
- return udpTestEnv{
- socketFD: socketFD,
- conn: conn,
- layers: testbench.Layers{
- expectedEthLayer(t, dut, socketFD, sendTo),
- ipLayer,
- &incomingUDP,
- },
- }
-}
-
-var _ protocolTest = (*udpTest)(nil)
-
-func (*udpTest) Name() string { return "udp" }
-
-func (test *udpTest) Send(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) {
- canSend := bindTo == nil || bindTo.Equal(net.IPv6zero) || sameIPVersion(sendTo, bindTo)
- expectPacket := canSend && !isRemoteAddr(dut, sendTo)
- switch {
- case bindTo.Equal(dut.Net.RemoteIPv4):
- // If we're explicitly bound to an interface's unicast address,
- // packets are always sent on that interface.
- case bindToDevice:
- // If we're explicitly bound to an interface, packets are always
- // sent on that interface.
- case !sendTo.Equal(net.IPv4bcast) && !sendTo.IsMulticast():
- // If we're not sending to limited broadcast, multicast, or local, the
- // route table will be consulted and packets will be sent on the correct
- // interface.
- default:
- expectPacket = false
- }
-
- wantErrno := unix.Errno(0)
- switch {
- case !canSend && bindTo.To4() != nil:
- wantErrno = unix.EAFNOSUPPORT
- case !canSend && bindTo.To4() == nil:
- wantErrno = unix.ENETUNREACH
- }
-
- // TODO(gvisor.dev/issue/5967): Remove this if statement once UDPv4 sockets
- // returns EAFNOSUPPORT after calling sendto with an IPv6 address.
- if dut.Uname.IsGvisor() && !canSend && bindTo.To4() != nil {
- wantErrno = unix.EINVAL
- }
-
- env := test.setup(t, dut, bindTo, sendTo, bindToDevice)
-
- for name, payload := range map[string][]byte{
- "empty": nil,
- "small": []byte("hello world"),
- "random1k": testbench.GenerateRandomPayload(t, maxUDPPayloadSize(bindTo)),
- } {
- t.Run(name, func(t *testing.T) {
- var destSockaddr unix.Sockaddr
- if sendTo4 := sendTo.To4(); sendTo4 != nil {
- addr := unix.SockaddrInet4{
- Port: int(env.conn.SrcPort(t)),
- }
- copy(addr.Addr[:], sendTo4)
- destSockaddr = &addr
- } else {
- addr := unix.SockaddrInet6{
- Port: int(env.conn.SrcPort(t)),
- ZoneId: dut.Net.RemoteDevID,
- }
- copy(addr.Addr[:], sendTo.To16())
- destSockaddr = &addr
- }
-
- // Tell the DUT to send a packet out the UDP socket.
- gotRet, gotErrno := dut.SendToWithErrno(context.Background(), t, env.socketFD, payload, 0, destSockaddr)
-
- if gotErrno != wantErrno {
- t.Fatalf("got dut.SendToWithErrno(_, _, %d, _, _, %s) = (_, %s), want = (_, %s)", env.socketFD, sendTo, gotErrno, wantErrno)
- }
- if wantErrno != 0 {
- return
- }
- if got, want := int(gotRet), len(payload); got != want {
- t.Fatalf("got dut.SendToWithErrno(_, _, %d, _, _, %s) = (%d, _), want = (%d, _)", env.socketFD, sendTo, got, want)
- }
-
- // Verify the test runner received a UDP packet with the
- // correct payload.
- want := append(env.layers, &testbench.Payload{
- Bytes: payload,
- })
- if got, ok := env.conn.ListenForFrame(t, want, time.Second); !ok && expectPacket {
- t.Fatalf("did not receive expected frame matching %s\nGot frames: %s", want, got)
- } else if ok && !expectPacket {
- matchedFrame := got[len(got)-1]
- t.Fatalf("got unexpected frame matching %s\nGot frame: %s", want, matchedFrame)
- }
- })
- }
-}
-
-func (test *udpTest) Receive(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) {
- subnetBroadcast := dut.Net.SubnetBroadcast()
-
- expectPacket := true
- switch {
- case bindTo.Equal(sendTo):
- case bindTo.Equal(net.IPv4zero) && sameIPVersion(bindTo, sendTo) && !sendTo.Equal(dut.Net.LocalIPv4):
- case bindTo.Equal(net.IPv6zero) && isBroadcast(dut, sendTo):
- case bindTo.Equal(net.IPv6zero) && isRemoteAddr(dut, sendTo):
- case bindTo.Equal(subnetBroadcast) && sendTo.Equal(subnetBroadcast):
- default:
- expectPacket = false
- }
-
- // TODO(gvisor.dev/issue/5956): Remove this if statement once gVisor
- // restricts ICMP sockets to receive only from unicast addresses.
- if (dut.Uname.IsGvisor() || dut.Uname.IsFuchsia()) && bindTo.Equal(net.IPv6zero) && sendTo.Equal(net.IPv4allsys) {
- expectPacket = true
- }
-
- env := test.setup(t, dut, bindTo, sendTo, bindToDevice)
- maxPayloadSize := maxUDPPayloadSize(bindTo)
-
- for name, payload := range map[string][]byte{
- "empty": nil,
- "small": []byte("hello world"),
- "random1k": testbench.GenerateRandomPayload(t, maxPayloadSize),
- } {
- t.Run(name, func(t *testing.T) {
- // Send a UDP packet from the test runner to the DUT.
- env.conn.SendFrame(t, env.layers, &testbench.Payload{Bytes: payload})
-
- // Verify the behavior of the ICMP socket on the DUT.
- if expectPacket {
- // Receive one extra byte to assert the length of the
- // packet received in the case where the packet contains
- // more data than expected.
- len := int32(len(payload)) + 1
- got, want := dut.Recv(t, env.socketFD, len, 0), payload
- if diff := cmp.Diff(want, got); diff != "" {
- t.Errorf("received payload does not match sent payload, diff (-want, +got):\n%s", diff)
- }
- } else {
- // Expected receive error, set a short receive timeout.
- dut.SetSockOptTimeval(
- t,
- env.socketFD,
- unix.SOL_SOCKET,
- unix.SO_RCVTIMEO,
- &unix.Timeval{
- Sec: 1,
- Usec: 0,
- },
- )
- ret, recvPayload, errno := dut.RecvWithErrno(context.Background(), t, env.socketFD, int32(maxPayloadSize), 0)
- if errno != unix.EAGAIN || errno != unix.EWOULDBLOCK {
- t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, recvPayload, errno)
- }
- }
- })
- }
-}
-
-func isBroadcast(dut testbench.DUT, ip net.IP) bool {
- return ip.Equal(net.IPv4bcast) || ip.Equal(dut.Net.SubnetBroadcast())
-}
-
-func isBroadcastOrMulticast(dut testbench.DUT, ip net.IP) bool {
- return isBroadcast(dut, ip) || ip.IsMulticast()
-}
-
-func sameIPVersion(a, b net.IP) bool {
- return (a.To4() == nil) == (b.To4() == nil)
-}
-
-func isRemoteAddr(dut testbench.DUT, ip net.IP) bool {
- return ip.Equal(dut.Net.RemoteIPv4) || ip.Equal(dut.Net.RemoteIPv6)
-}
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 fdabcf1ad..000000000
--- a/test/packetimpact/tests/icmpv6_param_problem_test.go
+++ /dev/null
@@ -1,74 +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 (
- "flag"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-// 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 := testbench.NewDUT(t)
- conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{})
- defer conn.Close(t)
- ipv6 := testbench.IPv6{
- // 254 is reserved and used for experimentation and testing. This should
- // cause an error.
- NextHeader: testbench.Uint8(254),
- }
- icmpv6 := testbench.ICMPv6{
- Type: testbench.ICMPv6Type(header.ICMPv6EchoRequest),
- Payload: []byte("hello world"),
- }
-
- toSend := conn.CreateFrame(t, testbench.Layers{&ipv6}, &icmpv6)
- conn.SendFrame(t, 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)
- }
-
- expectedICMPv6 := testbench.ICMPv6{
- Type: testbench.ICMPv6Type(header.ICMPv6ParamProblem),
- Payload: expectedPayload,
- // The problematic field is the NextHeader.
- Pointer: testbench.Uint32(header.IPv6NextHeaderOffset),
- }
-
- paramProblem := testbench.Layers{
- &testbench.Ether{},
- &testbench.IPv6{},
- &expectedICMPv6,
- }
- timeout := time.Second
- if _, err := conn.ExpectFrame(t, paramProblem, timeout); err != nil {
- t.Errorf("expected %s within %s but got none: %s", paramProblem, timeout, err)
- }
-}
diff --git a/test/packetimpact/tests/ipv4_fragment_reassembly_test.go b/test/packetimpact/tests/ipv4_fragment_reassembly_test.go
deleted file mode 100644
index 707f0f1f5..000000000
--- a/test/packetimpact/tests/ipv4_fragment_reassembly_test.go
+++ /dev/null
@@ -1,175 +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 ipv4_fragment_reassembly_test
-
-import (
- "flag"
- "math/rand"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-type fragmentInfo struct {
- offset uint16
- size uint16
- more uint8
- id uint16
-}
-
-func TestIPv4FragmentReassembly(t *testing.T) {
- icmpv4ProtoNum := uint8(header.ICMPv4ProtocolNumber)
-
- tests := []struct {
- description string
- ipPayloadLen int
- fragments []fragmentInfo
- expectReply bool
- }{
- {
- description: "basic reassembly",
- ipPayloadLen: 3000,
- fragments: []fragmentInfo{
- {offset: 0, size: 1000, id: 5, more: header.IPv4FlagMoreFragments},
- {offset: 1000, size: 1000, id: 5, more: header.IPv4FlagMoreFragments},
- {offset: 2000, size: 1000, id: 5, more: 0},
- },
- expectReply: true,
- },
- {
- description: "out of order fragments",
- ipPayloadLen: 3000,
- fragments: []fragmentInfo{
- {offset: 2000, size: 1000, id: 6, more: 0},
- {offset: 0, size: 1000, id: 6, more: header.IPv4FlagMoreFragments},
- {offset: 1000, size: 1000, id: 6, more: header.IPv4FlagMoreFragments},
- },
- expectReply: true,
- },
- {
- description: "duplicated fragments",
- ipPayloadLen: 3000,
- fragments: []fragmentInfo{
- {offset: 0, size: 1000, id: 7, more: header.IPv4FlagMoreFragments},
- {offset: 1000, size: 1000, id: 7, more: header.IPv4FlagMoreFragments},
- {offset: 1000, size: 1000, id: 7, more: header.IPv4FlagMoreFragments},
- {offset: 2000, size: 1000, id: 7, more: 0},
- },
- expectReply: true,
- },
- {
- description: "fragment subset",
- ipPayloadLen: 3000,
- fragments: []fragmentInfo{
- {offset: 0, size: 1000, id: 8, more: header.IPv4FlagMoreFragments},
- {offset: 1000, size: 1000, id: 8, more: header.IPv4FlagMoreFragments},
- {offset: 512, size: 256, id: 8, more: header.IPv4FlagMoreFragments},
- {offset: 2000, size: 1000, id: 8, more: 0},
- },
- expectReply: true,
- },
- {
- description: "fragment overlap",
- ipPayloadLen: 3000,
- fragments: []fragmentInfo{
- {offset: 0, size: 1000, id: 9, more: header.IPv4FlagMoreFragments},
- {offset: 1512, size: 1000, id: 9, more: header.IPv4FlagMoreFragments},
- {offset: 1000, size: 1000, id: 9, more: header.IPv4FlagMoreFragments},
- {offset: 2000, size: 1000, id: 9, more: 0},
- },
- expectReply: false,
- },
- }
-
- for _, test := range tests {
- t.Run(test.description, func(t *testing.T) {
- dut := testbench.NewDUT(t)
- conn := dut.Net.NewIPv4Conn(t, testbench.IPv4{}, testbench.IPv4{})
- defer conn.Close(t)
-
- data := make([]byte, test.ipPayloadLen)
- icmp := header.ICMPv4(data[:header.ICMPv4MinimumSize])
- icmp.SetType(header.ICMPv4Echo)
- icmp.SetCode(header.ICMPv4UnusedCode)
- icmp.SetChecksum(0)
- icmp.SetSequence(0)
- icmp.SetIdent(0)
- originalPayload := data[header.ICMPv4MinimumSize:]
- if _, err := rand.Read(originalPayload); err != nil {
- t.Fatalf("rand.Read: %s", err)
- }
- cksum := header.ICMPv4Checksum(icmp, header.Checksum(originalPayload, 0 /* initial */))
- icmp.SetChecksum(cksum)
-
- for _, fragment := range test.fragments {
- conn.Send(t,
- testbench.IPv4{
- Protocol: &icmpv4ProtoNum,
- FragmentOffset: testbench.Uint16(fragment.offset),
- Flags: testbench.Uint8(fragment.more),
- ID: testbench.Uint16(fragment.id),
- },
- &testbench.Payload{
- Bytes: data[fragment.offset:][:fragment.size],
- })
- }
-
- var bytesReceived int
- reassembledPayload := make([]byte, test.ipPayloadLen)
- // We are sending a packet fragmented into smaller parts but the
- // response may also be large enough to require fragmentation.
- // Therefore we only look for payload for an IPv4 packet not ICMP.
- for {
- incomingFrame, err := conn.ExpectFrame(t, testbench.Layers{
- &testbench.Ether{},
- &testbench.IPv4{},
- }, time.Second)
- if err != nil {
- // Either an unexpected frame was received, or none at all.
- if test.expectReply && bytesReceived < test.ipPayloadLen {
- t.Fatalf("received %d bytes out of %d, then conn.ExpectFrame(_, _, time.Second) failed with %s", bytesReceived, test.ipPayloadLen, err)
- }
- break
- }
- if !test.expectReply {
- t.Fatalf("unexpected reply received:\n%s", incomingFrame)
- }
- // We only asked for Ethernet and IPv4 so the rest should be payload.
- ipPayload, err := incomingFrame[2 /* Payload */].ToBytes()
- if err != nil {
- t.Fatalf("failed to parse payload: incomingPacket[2].ToBytes() = (_, %s)", err)
- }
- offset := *incomingFrame[1 /* IPv4 */].(*testbench.IPv4).FragmentOffset
- if copied := copy(reassembledPayload[offset:], ipPayload); copied != len(ipPayload) {
- t.Fatalf("wrong number of bytes copied into reassembledPayload: got = %d, want = %d", copied, len(ipPayload))
- }
- bytesReceived += len(ipPayload)
- }
-
- if test.expectReply {
- if diff := cmp.Diff(originalPayload, reassembledPayload[header.ICMPv4MinimumSize:]); diff != "" {
- t.Fatalf("reassembledPayload mismatch (-want +got):\n%s", diff)
- }
- }
- })
- }
-}
diff --git a/test/packetimpact/tests/ipv4_id_uniqueness_test.go b/test/packetimpact/tests/ipv4_id_uniqueness_test.go
deleted file mode 100644
index 2b69ceecb..000000000
--- a/test/packetimpact/tests/ipv4_id_uniqueness_test.go
+++ /dev/null
@@ -1,121 +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 ipv4_id_uniqueness_test
-
-import (
- "context"
- "flag"
- "fmt"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-func recvTCPSegment(t *testing.T, conn *testbench.TCPIPv4, expect *testbench.TCP, expectPayload *testbench.Payload) (uint16, error) {
- layers, err := conn.ExpectData(t, expect, expectPayload, time.Second)
- if err != nil {
- return 0, fmt.Errorf("failed to receive TCP segment: %s", err)
- }
- if len(layers) < 2 {
- return 0, fmt.Errorf("got packet with layers: %v, expected to have at least 2 layers (link and network)", layers)
- }
- ipv4, ok := layers[1].(*testbench.IPv4)
- if !ok {
- return 0, fmt.Errorf("got network layer: %T, expected: *IPv4", layers[1])
- }
- if *ipv4.Flags&header.IPv4FlagDontFragment != 0 {
- return 0, fmt.Errorf("got IPv4 DF=1, expected DF=0")
- }
- return *ipv4.ID, nil
-}
-
-// RFC 6864 section 4.2 states: "The IPv4 ID of non-atomic datagrams MUST NOT
-// be reused when sending a copy of an earlier non-atomic datagram."
-//
-// This test creates a TCP connection, uses the IP_MTU_DISCOVER socket option
-// to force the DF bit to be 0, and checks that a retransmitted segment has a
-// different IPv4 Identification value than the original segment.
-func TestIPv4RetransmitIdentificationUniqueness(t *testing.T) {
- for _, tc := range []struct {
- name string
- payload []byte
- }{
- {"SmallPayload", []byte("sample data")},
- // 512 bytes is chosen because sending more than this in a single segment
- // causes the retransmission to send less than the original amount.
- {"512BytePayload", testbench.GenerateRandomPayload(t, 512)},
- } {
- t.Run(tc.name, func(t *testing.T) {
- dut := testbench.NewDUT(t)
- listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(t, listenFD)
-
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- defer conn.Close(t)
-
- conn.Connect(t)
- remoteFD, _ := dut.Accept(t, listenFD)
- defer dut.Close(t, remoteFD)
-
- dut.SetSockOptInt(t, remoteFD, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
-
- // TODO(b/129291778) The following socket option clears the DF bit on
- // IP packets sent over the socket, and is currently not supported by
- // gVisor. gVisor by default sends packets with DF=0 anyway, so the
- // socket option being not supported does not affect the operation of
- // this test. Once the socket option is supported, the following call
- // can be changed to simply assert success.
- ret, errno := dut.SetSockOptIntWithErrno(context.Background(), t, remoteFD, unix.IPPROTO_IP, linux.IP_MTU_DISCOVER, linux.IP_PMTUDISC_DONT)
- // Fuchsia will return ENOPROTOPT errno.
- if ret == -1 && errno != unix.ENOPROTOOPT {
- t.Fatalf("failed to set IP_MTU_DISCOVER socket option to IP_PMTUDISC_DONT: %s", errno)
- }
-
- samplePayload := &testbench.Payload{Bytes: tc.payload}
-
- dut.Send(t, remoteFD, tc.payload, 0)
- if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil {
- t.Fatalf("failed to receive TCP segment sent for RTT calculation: %s", err)
- }
- // Let the DUT estimate RTO with RTT from the DATA-ACK.
- // TODO(gvisor.dev/issue/2685) Estimate RTO during handshake, after which
- // we can skip sending this ACK.
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
-
- dut.Send(t, remoteFD, tc.payload, 0)
- expectTCP := &testbench.TCP{SeqNum: testbench.Uint32(uint32(*conn.RemoteSeqNum(t)))}
- originalID, err := recvTCPSegment(t, &conn, expectTCP, samplePayload)
- if err != nil {
- t.Fatalf("failed to receive TCP segment: %s", err)
- }
-
- retransmitID, err := recvTCPSegment(t, &conn, expectTCP, samplePayload)
- if err != nil {
- t.Fatalf("failed to receive retransmitted TCP segment: %s", err)
- }
- if originalID == retransmitID {
- t.Fatalf("unexpectedly got retransmitted TCP segment with same IPv4 ID field=%d", originalID)
- }
- })
- }
-}
diff --git a/test/packetimpact/tests/ipv6_fragment_icmp_error_test.go b/test/packetimpact/tests/ipv6_fragment_icmp_error_test.go
deleted file mode 100644
index 4034a128e..000000000
--- a/test/packetimpact/tests/ipv6_fragment_icmp_error_test.go
+++ /dev/null
@@ -1,356 +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 ipv6_fragment_icmp_error_test
-
-import (
- "flag"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-const (
- data = "IPV6_PROTOCOL_TESTER_FOR_FRAGMENT"
- fragmentID = 1
- reassemblyTimeout = ipv6.ReassembleTimeout + 5*time.Second
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-func fragmentedICMPEchoRequest(t *testing.T, n *testbench.DUTTestNet, conn *testbench.IPv6Conn, firstPayloadLength uint16, payload []byte, secondFragmentOffset uint16) ([]testbench.Layers, [][]byte) {
- t.Helper()
-
- icmpv6Header := header.ICMPv6(make([]byte, header.ICMPv6EchoMinimumSize))
- icmpv6Header.SetType(header.ICMPv6EchoRequest)
- icmpv6Header.SetCode(header.ICMPv6UnusedCode)
- icmpv6Header.SetIdent(0)
- icmpv6Header.SetSequence(0)
- cksum := header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
- Header: icmpv6Header,
- Src: tcpip.Address(n.LocalIPv6),
- Dst: tcpip.Address(n.RemoteIPv6),
- PayloadCsum: header.Checksum(payload, 0 /* initial */),
- PayloadLen: len(payload),
- })
- icmpv6Header.SetChecksum(cksum)
- icmpv6Bytes := append([]byte(icmpv6Header), payload...)
-
- icmpv6ProtoNum := header.IPv6ExtensionHeaderIdentifier(header.ICMPv6ProtocolNumber)
-
- firstFragment := conn.CreateFrame(t, testbench.Layers{&testbench.IPv6{}},
- &testbench.IPv6FragmentExtHdr{
- NextHeader: &icmpv6ProtoNum,
- FragmentOffset: testbench.Uint16(0),
- MoreFragments: testbench.Bool(true),
- Identification: testbench.Uint32(fragmentID),
- },
- &testbench.Payload{
- Bytes: icmpv6Bytes[:header.ICMPv6PayloadOffset+firstPayloadLength],
- },
- )
- firstIPv6 := firstFragment[1:]
- firstIPv6Bytes, err := firstIPv6.ToBytes()
- if err != nil {
- t.Fatalf("failed to convert first %s to bytes: %s", firstIPv6, err)
- }
-
- secondFragment := conn.CreateFrame(t, testbench.Layers{&testbench.IPv6{}},
- &testbench.IPv6FragmentExtHdr{
- NextHeader: &icmpv6ProtoNum,
- FragmentOffset: testbench.Uint16(secondFragmentOffset),
- MoreFragments: testbench.Bool(false),
- Identification: testbench.Uint32(fragmentID),
- },
- &testbench.Payload{
- Bytes: icmpv6Bytes[header.ICMPv6PayloadOffset+firstPayloadLength:],
- },
- )
- secondIPv6 := secondFragment[1:]
- secondIPv6Bytes, err := secondIPv6.ToBytes()
- if err != nil {
- t.Fatalf("failed to convert second %s to bytes: %s", secondIPv6, err)
- }
-
- return []testbench.Layers{firstFragment, secondFragment}, [][]byte{firstIPv6Bytes, secondIPv6Bytes}
-}
-
-func TestIPv6ICMPEchoRequestFragmentReassembly(t *testing.T) {
- tests := []struct {
- name string
- firstPayloadLength uint16
- payload []byte
- secondFragmentOffset uint16
- sendFrameOrder []int
- }{
- {
- name: "reassemble two fragments",
- firstPayloadLength: 8,
- payload: []byte(data)[:20],
- secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 8) / 8,
- sendFrameOrder: []int{1, 2},
- },
- {
- name: "reassemble two fragments in reverse order",
- firstPayloadLength: 8,
- payload: []byte(data)[:20],
- secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 8) / 8,
- sendFrameOrder: []int{2, 1},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- t.Parallel()
- dut := testbench.NewDUT(t)
- conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{})
- defer conn.Close(t)
-
- fragments, _ := fragmentedICMPEchoRequest(t, dut.Net, &conn, test.firstPayloadLength, test.payload, test.secondFragmentOffset)
-
- for _, i := range test.sendFrameOrder {
- conn.SendFrame(t, fragments[i-1])
- }
-
- gotEchoReply, err := conn.ExpectFrame(t, testbench.Layers{
- &testbench.Ether{},
- &testbench.IPv6{},
- &testbench.ICMPv6{
- Type: testbench.ICMPv6Type(header.ICMPv6EchoReply),
- Code: testbench.ICMPv6Code(header.ICMPv6UnusedCode),
- },
- }, time.Second)
- if err != nil {
- t.Fatalf("didn't receive an ICMPv6 Echo Reply: %s", err)
- }
- gotPayload, err := gotEchoReply[len(gotEchoReply)-1].ToBytes()
- if err != nil {
- t.Fatalf("failed to convert ICMPv6 to bytes: %s", err)
- }
- icmpPayload := gotPayload[header.ICMPv6EchoMinimumSize:]
- wantPayload := test.payload
- if diff := cmp.Diff(wantPayload, icmpPayload); diff != "" {
- t.Fatalf("payload mismatch (-want +got):\n%s", diff)
- }
- })
- }
-}
-
-func TestIPv6FragmentReassemblyTimeout(t *testing.T) {
- type icmpFramePattern struct {
- typ header.ICMPv6Type
- code header.ICMPv6Code
- }
-
- type icmpReassemblyTimeoutDetail struct {
- payloadFragment int // 1: first fragment, 2: second fragnemt.
- }
-
- tests := []struct {
- name string
- firstPayloadLength uint16
- payload []byte
- secondFragmentOffset uint16
- sendFrameOrder []int
- replyFilter icmpFramePattern
- expectErrorReply bool
- expectICMPReassemblyTimeout icmpReassemblyTimeoutDetail
- }{
- {
- name: "reassembly timeout (first fragment only)",
- firstPayloadLength: 8,
- payload: []byte(data)[:20],
- secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 8) / 8,
- sendFrameOrder: []int{1},
- replyFilter: icmpFramePattern{
- typ: header.ICMPv6TimeExceeded,
- code: header.ICMPv6ReassemblyTimeout,
- },
- expectErrorReply: true,
- expectICMPReassemblyTimeout: icmpReassemblyTimeoutDetail{
- payloadFragment: 1,
- },
- },
- {
- name: "reassembly timeout (second fragment only)",
- firstPayloadLength: 8,
- payload: []byte(data)[:20],
- secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 8) / 8,
- sendFrameOrder: []int{2},
- replyFilter: icmpFramePattern{
- typ: header.ICMPv6TimeExceeded,
- code: header.ICMPv6ReassemblyTimeout,
- },
- expectErrorReply: false,
- },
- {
- name: "reassembly timeout (two fragments with a gap)",
- firstPayloadLength: 8,
- payload: []byte(data)[:20],
- secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 16) / 8,
- sendFrameOrder: []int{1, 2},
- replyFilter: icmpFramePattern{
- typ: header.ICMPv6TimeExceeded,
- code: header.ICMPv6ReassemblyTimeout,
- },
- expectErrorReply: true,
- expectICMPReassemblyTimeout: icmpReassemblyTimeoutDetail{
- payloadFragment: 1,
- },
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- t.Parallel()
- dut := testbench.NewDUT(t)
- conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{})
- defer conn.Close(t)
-
- fragments, ipv6Bytes := fragmentedICMPEchoRequest(t, dut.Net, &conn, test.firstPayloadLength, test.payload, test.secondFragmentOffset)
-
- for _, i := range test.sendFrameOrder {
- conn.SendFrame(t, fragments[i-1])
- }
-
- gotErrorMessage, err := conn.ExpectFrame(t, testbench.Layers{
- &testbench.Ether{},
- &testbench.IPv6{},
- &testbench.ICMPv6{
- Type: testbench.ICMPv6Type(test.replyFilter.typ),
- Code: testbench.ICMPv6Code(test.replyFilter.code),
- },
- }, reassemblyTimeout)
- if !test.expectErrorReply {
- if err == nil {
- t.Fatalf("shouldn't receive an ICMPv6 Error Message with type=%d and code=%d", test.replyFilter.typ, test.replyFilter.code)
- }
- return
- }
- if err != nil {
- t.Fatalf("didn't receive an ICMPv6 Error Message with type=%d and code=%d: err", test.replyFilter.typ, test.replyFilter.code, err)
- }
- gotPayload, err := gotErrorMessage[len(gotErrorMessage)-1].ToBytes()
- if err != nil {
- t.Fatalf("failed to convert ICMPv6 to bytes: %s", err)
- }
- icmpPayload := gotPayload[header.ICMPv6ErrorHeaderSize:]
- wantPayload := ipv6Bytes[test.expectICMPReassemblyTimeout.payloadFragment-1]
- if diff := cmp.Diff(wantPayload, icmpPayload); diff != "" {
- t.Fatalf("payload mismatch (-want +got):\n%s", diff)
- }
- })
- }
-}
-
-func TestIPv6FragmentParamProblem(t *testing.T) {
- type icmpFramePattern struct {
- typ header.ICMPv6Type
- code header.ICMPv6Code
- }
-
- type icmpParamProblemDetail struct {
- pointer uint32
- payloadFragment int // 1: first fragment, 2: second fragnemt.
- }
-
- tests := []struct {
- name string
- firstPayloadLength uint16
- payload []byte
- secondFragmentOffset uint16
- sendFrameOrder []int
- replyFilter icmpFramePattern
- expectICMPParamProblem icmpParamProblemDetail
- }{
- {
- name: "payload size not a multiple of 8",
- firstPayloadLength: 9,
- payload: []byte(data)[:20],
- secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 8) / 8,
- sendFrameOrder: []int{1},
- replyFilter: icmpFramePattern{
- typ: header.ICMPv6ParamProblem,
- code: header.ICMPv6ErroneousHeader,
- },
- expectICMPParamProblem: icmpParamProblemDetail{
- pointer: 4,
- payloadFragment: 1,
- },
- },
- {
- name: "payload length error",
- firstPayloadLength: 16,
- payload: []byte(data)[:33],
- secondFragmentOffset: 65520 / 8,
- sendFrameOrder: []int{1, 2},
- replyFilter: icmpFramePattern{
- typ: header.ICMPv6ParamProblem,
- code: header.ICMPv6ErroneousHeader,
- },
- expectICMPParamProblem: icmpParamProblemDetail{
- pointer: 42,
- payloadFragment: 2,
- },
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- t.Parallel()
- dut := testbench.NewDUT(t)
- conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{})
- defer conn.Close(t)
-
- fragments, ipv6Bytes := fragmentedICMPEchoRequest(t, dut.Net, &conn, test.firstPayloadLength, test.payload, test.secondFragmentOffset)
-
- for _, i := range test.sendFrameOrder {
- conn.SendFrame(t, fragments[i-1])
- }
-
- gotErrorMessage, err := conn.ExpectFrame(t, testbench.Layers{
- &testbench.Ether{},
- &testbench.IPv6{},
- &testbench.ICMPv6{
- Type: testbench.ICMPv6Type(test.replyFilter.typ),
- Code: testbench.ICMPv6Code(test.replyFilter.code),
- },
- }, time.Second)
- if err != nil {
- t.Fatalf("didn't receive an ICMPv6 Error Message with type=%d and code=%d: err", test.replyFilter.typ, test.replyFilter.code, err)
- }
- gotPayload, err := gotErrorMessage[len(gotErrorMessage)-1].ToBytes()
- if err != nil {
- t.Fatalf("failed to convert ICMPv6 to bytes: %s", err)
- }
- gotPointer := header.ICMPv6(gotPayload).TypeSpecific()
- wantPointer := test.expectICMPParamProblem.pointer
- if gotPointer != wantPointer {
- t.Fatalf("got pointer = %d, want = %d", gotPointer, wantPointer)
- }
- icmpPayload := gotPayload[header.ICMPv6ErrorHeaderSize:]
- wantPayload := ipv6Bytes[test.expectICMPParamProblem.payloadFragment-1]
- if diff := cmp.Diff(wantPayload, icmpPayload); diff != "" {
- t.Fatalf("payload mismatch (-want +got):\n%s", diff)
- }
- })
- }
-}
diff --git a/test/packetimpact/tests/ipv6_fragment_reassembly_test.go b/test/packetimpact/tests/ipv6_fragment_reassembly_test.go
deleted file mode 100644
index db6195dc9..000000000
--- a/test/packetimpact/tests/ipv6_fragment_reassembly_test.go
+++ /dev/null
@@ -1,182 +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 ipv6_fragment_reassembly_test
-
-import (
- "flag"
- "math/rand"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-type fragmentInfo struct {
- offset uint16
- size uint16
- more bool
- id uint32
-}
-
-func TestIPv6FragmentReassembly(t *testing.T) {
- icmpv6ProtoNum := header.IPv6ExtensionHeaderIdentifier(header.ICMPv6ProtocolNumber)
-
- tests := []struct {
- description string
- ipPayloadLen int
- fragments []fragmentInfo
- expectReply bool
- }{
- {
- description: "basic reassembly",
- ipPayloadLen: 3000,
- fragments: []fragmentInfo{
- {offset: 0, size: 1000, id: 100, more: true},
- {offset: 1000, size: 1000, id: 100, more: true},
- {offset: 2000, size: 1000, id: 100, more: false},
- },
- expectReply: true,
- },
- {
- description: "out of order fragments",
- ipPayloadLen: 3000,
- fragments: []fragmentInfo{
- {offset: 0, size: 1000, id: 101, more: true},
- {offset: 2000, size: 1000, id: 101, more: false},
- {offset: 1000, size: 1000, id: 101, more: true},
- },
- expectReply: true,
- },
- {
- description: "duplicated fragments",
- ipPayloadLen: 3000,
- fragments: []fragmentInfo{
- {offset: 0, size: 1000, id: 102, more: true},
- {offset: 1000, size: 1000, id: 102, more: true},
- {offset: 1000, size: 1000, id: 102, more: true},
- {offset: 2000, size: 1000, id: 102, more: false},
- },
- expectReply: true,
- },
- {
- description: "fragment subset",
- ipPayloadLen: 3000,
- fragments: []fragmentInfo{
- {offset: 0, size: 1000, id: 103, more: true},
- {offset: 1000, size: 1000, id: 103, more: true},
- {offset: 512, size: 256, id: 103, more: true},
- {offset: 2000, size: 1000, id: 103, more: false},
- },
- expectReply: true,
- },
- {
- description: "fragment overlap",
- ipPayloadLen: 3000,
- fragments: []fragmentInfo{
- {offset: 0, size: 1000, id: 104, more: true},
- {offset: 1512, size: 1000, id: 104, more: true},
- {offset: 1000, size: 1000, id: 104, more: true},
- {offset: 2000, size: 1000, id: 104, more: false},
- },
- expectReply: false,
- },
- }
-
- for _, test := range tests {
- t.Run(test.description, func(t *testing.T) {
- dut := testbench.NewDUT(t)
- conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{})
- defer conn.Close(t)
-
- lIP := tcpip.Address(dut.Net.LocalIPv6)
- rIP := tcpip.Address(dut.Net.RemoteIPv6)
-
- data := make([]byte, test.ipPayloadLen)
- icmp := header.ICMPv6(data[:header.ICMPv6HeaderSize])
- icmp.SetType(header.ICMPv6EchoRequest)
- icmp.SetCode(header.ICMPv6UnusedCode)
- icmp.SetChecksum(0)
- originalPayload := data[header.ICMPv6HeaderSize:]
- if _, err := rand.Read(originalPayload); err != nil {
- t.Fatalf("rand.Read: %s", err)
- }
-
- cksum := header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
- Header: icmp,
- Src: lIP,
- Dst: rIP,
- PayloadCsum: header.Checksum(originalPayload, 0 /* initial */),
- PayloadLen: len(originalPayload),
- })
- icmp.SetChecksum(cksum)
-
- for _, fragment := range test.fragments {
- conn.Send(t, testbench.IPv6{},
- &testbench.IPv6FragmentExtHdr{
- NextHeader: &icmpv6ProtoNum,
- FragmentOffset: testbench.Uint16(fragment.offset / header.IPv6FragmentExtHdrFragmentOffsetBytesPerUnit),
- MoreFragments: testbench.Bool(fragment.more),
- Identification: testbench.Uint32(fragment.id),
- },
- &testbench.Payload{
- Bytes: data[fragment.offset:][:fragment.size],
- })
- }
-
- var bytesReceived int
- reassembledPayload := make([]byte, test.ipPayloadLen)
- for {
- incomingFrame, err := conn.ExpectFrame(t, testbench.Layers{
- &testbench.Ether{},
- &testbench.IPv6{},
- &testbench.IPv6FragmentExtHdr{},
- }, time.Second)
- if err != nil {
- // Either an unexpected frame was received, or none at all.
- if test.expectReply && bytesReceived < test.ipPayloadLen {
- t.Fatalf("received %d bytes out of %d, then conn.ExpectFrame(_, _, time.Second) failed with %s", bytesReceived, test.ipPayloadLen, err)
- }
- break
- }
- if !test.expectReply {
- t.Fatalf("unexpected reply received:\n%s", incomingFrame)
- }
- ipPayload, err := incomingFrame[3 /* Payload */].ToBytes()
- if err != nil {
- t.Fatalf("failed to parse ICMPv6 header: incomingPacket[3].ToBytes() = (_, %s)", err)
- }
- offset := *incomingFrame[2 /* IPv6FragmentExtHdr */].(*testbench.IPv6FragmentExtHdr).FragmentOffset
- offset *= header.IPv6FragmentExtHdrFragmentOffsetBytesPerUnit
- if copied := copy(reassembledPayload[offset:], ipPayload); copied != len(ipPayload) {
- t.Fatalf("wrong number of bytes copied into reassembledPayload: got = %d, want = %d", copied, len(ipPayload))
- }
- bytesReceived += len(ipPayload)
- }
-
- if test.expectReply {
- if diff := cmp.Diff(originalPayload, reassembledPayload[header.ICMPv6HeaderSize:]); diff != "" {
- t.Fatalf("reassembledPayload mismatch (-want +got):\n%s", diff)
- }
- }
- })
- }
-}
diff --git a/test/packetimpact/tests/ipv6_unknown_options_action_test.go b/test/packetimpact/tests/ipv6_unknown_options_action_test.go
deleted file mode 100644
index d762c43a7..000000000
--- a/test/packetimpact/tests/ipv6_unknown_options_action_test.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 ipv6_unknown_options_action_test
-
-import (
- "flag"
- "net"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-func mkHopByHopOptionsExtHdr(optType byte) testbench.Layer {
- return &testbench.IPv6HopByHopOptionsExtHdr{
- Options: []byte{optType, 0x04, 0x00, 0x00, 0x00, 0x00},
- }
-}
-
-func mkDestinationOptionsExtHdr(optType byte) testbench.Layer {
- return &testbench.IPv6DestinationOptionsExtHdr{
- Options: []byte{optType, 0x04, 0x00, 0x00, 0x00, 0x00},
- }
-}
-
-func optionTypeFromAction(action header.IPv6OptionUnknownAction) byte {
- return byte(action << 6)
-}
-
-func TestIPv6UnknownOptionAction(t *testing.T) {
- for _, tt := range []struct {
- description string
- mkExtHdr func(optType byte) testbench.Layer
- action header.IPv6OptionUnknownAction
- multicastDst bool
- wantICMPv6 bool
- }{
- {
- description: "0b00/hbh",
- mkExtHdr: mkHopByHopOptionsExtHdr,
- action: header.IPv6OptionUnknownActionSkip,
- multicastDst: false,
- wantICMPv6: false,
- },
- {
- description: "0b01/hbh",
- mkExtHdr: mkHopByHopOptionsExtHdr,
- action: header.IPv6OptionUnknownActionDiscard,
- multicastDst: false,
- wantICMPv6: false,
- },
- {
- description: "0b10/hbh/unicast",
- mkExtHdr: mkHopByHopOptionsExtHdr,
- action: header.IPv6OptionUnknownActionDiscardSendICMP,
- multicastDst: false,
- wantICMPv6: true,
- },
- {
- description: "0b10/hbh/multicast",
- mkExtHdr: mkHopByHopOptionsExtHdr,
- action: header.IPv6OptionUnknownActionDiscardSendICMP,
- multicastDst: true,
- wantICMPv6: true,
- },
- {
- description: "0b11/hbh/unicast",
- mkExtHdr: mkHopByHopOptionsExtHdr,
- action: header.IPv6OptionUnknownActionDiscardSendICMPNoMulticastDest,
- multicastDst: false,
- wantICMPv6: true,
- },
- {
- description: "0b11/hbh/multicast",
- mkExtHdr: mkHopByHopOptionsExtHdr,
- action: header.IPv6OptionUnknownActionDiscardSendICMPNoMulticastDest,
- multicastDst: true,
- wantICMPv6: false,
- },
- {
- description: "0b00/destination",
- mkExtHdr: mkDestinationOptionsExtHdr,
- action: header.IPv6OptionUnknownActionSkip,
- multicastDst: false,
- wantICMPv6: false,
- },
- {
- description: "0b01/destination",
- mkExtHdr: mkDestinationOptionsExtHdr,
- action: header.IPv6OptionUnknownActionDiscard,
- multicastDst: false,
- wantICMPv6: false,
- },
- {
- description: "0b10/destination/unicast",
- mkExtHdr: mkDestinationOptionsExtHdr,
- action: header.IPv6OptionUnknownActionDiscardSendICMP,
- multicastDst: false,
- wantICMPv6: true,
- },
- {
- description: "0b10/destination/multicast",
- mkExtHdr: mkDestinationOptionsExtHdr,
- action: header.IPv6OptionUnknownActionDiscardSendICMP,
- multicastDst: true,
- wantICMPv6: true,
- },
- {
- description: "0b11/destination/unicast",
- mkExtHdr: mkDestinationOptionsExtHdr,
- action: header.IPv6OptionUnknownActionDiscardSendICMPNoMulticastDest,
- multicastDst: false,
- wantICMPv6: true,
- },
- {
- description: "0b11/destination/multicast",
- mkExtHdr: mkDestinationOptionsExtHdr,
- action: header.IPv6OptionUnknownActionDiscardSendICMPNoMulticastDest,
- multicastDst: true,
- wantICMPv6: false,
- },
- } {
- t.Run(tt.description, func(t *testing.T) {
- dut := testbench.NewDUT(t)
- conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{})
- defer conn.Close(t)
-
- outgoingOverride := testbench.Layers{}
- if tt.multicastDst {
- outgoingOverride = testbench.Layers{&testbench.IPv6{
- DstAddr: testbench.Address(tcpip.Address(net.ParseIP("ff02::1"))),
- }}
- }
-
- outgoing := conn.CreateFrame(t, outgoingOverride, tt.mkExtHdr(optionTypeFromAction(tt.action)))
- conn.SendFrame(t, outgoing)
- ipv6Sent := outgoing[1:]
- icmpv6Payload, err := ipv6Sent.ToBytes()
- if err != nil {
- t.Fatalf("failed to serialize the outgoing packet: %s", err)
- }
- gotICMPv6, err := conn.ExpectFrame(t, testbench.Layers{
- &testbench.Ether{},
- &testbench.IPv6{},
- &testbench.ICMPv6{
- Type: testbench.ICMPv6Type(header.ICMPv6ParamProblem),
- Code: testbench.ICMPv6Code(header.ICMPv6UnknownOption),
- // The pointer in the ICMPv6 parameter problem message
- // should point to the option type of the unknown option. In
- // our test case, it is the first option in the extension
- // header whose option type is 2 bytes after the IPv6 header
- // (after NextHeader and ExtHdrLen).
- Pointer: testbench.Uint32(header.IPv6MinimumSize + 2),
- Payload: icmpv6Payload,
- },
- }, time.Second)
- if tt.wantICMPv6 && err != nil {
- t.Fatalf("expected ICMPv6 Parameter Problem but got none: %s", err)
- }
- if !tt.wantICMPv6 && gotICMPv6 != nil {
- t.Fatalf("expected no ICMPv6 Parameter Problem but got one: %s", gotICMPv6)
- }
- })
- }
-}
diff --git a/test/packetimpact/tests/tcp_connect_icmp_error_test.go b/test/packetimpact/tests/tcp_connect_icmp_error_test.go
deleted file mode 100644
index 15d603328..000000000
--- a/test/packetimpact/tests/tcp_connect_icmp_error_test.go
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright 2021 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_connect_icmp_error_test
-
-import (
- "flag"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-func sendICMPError(t *testing.T, conn *testbench.TCPIPv4, tcp *testbench.TCP) {
- t.Helper()
-
- icmpPayload := testbench.Layers{tcp.Prev(), tcp}
- bytes, err := icmpPayload.ToBytes()
- if err != nil {
- t.Fatalf("got icmpPayload.ToBytes() = (_, %s), want = (_, nil)", err)
- }
-
- layers := conn.CreateFrame(t, nil)
- layers[len(layers)-1] = &testbench.ICMPv4{
- Type: testbench.ICMPv4Type(header.ICMPv4DstUnreachable),
- Code: testbench.ICMPv4Code(header.ICMPv4HostUnreachable),
- Payload: bytes,
- }
- conn.SendFrameStateless(t, layers)
-}
-
-// TestTCPConnectICMPError tests for the handshake to fail and the socket state
-// cleaned up on receiving an ICMP error.
-func TestTCPConnectICMPError(t *testing.T) {
- dut := testbench.NewDUT(t)
-
- clientFD, clientPort := dut.CreateBoundSocket(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, dut.Net.RemoteIPv4)
- port := uint16(9001)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{SrcPort: &port, DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort, DstPort: &port})
- defer conn.Close(t)
- sa := unix.SockaddrInet4{Port: int(port)}
- copy(sa.Addr[:], dut.Net.LocalIPv4)
- // Bring the dut to SYN-SENT state with a non-blocking connect.
- dut.Connect(t, clientFD, &sa)
- tcp, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, time.Second)
- if err != nil {
- t.Fatalf("expected SYN, %s", err)
- }
-
- // Continuously try to read the ICMP error in an attempt to trigger a race
- // condition.
- start := make(chan struct{})
- done := make(chan struct{})
- go func() {
- defer close(done)
-
- close(start)
- for {
- select {
- case <-done:
- return
- default:
- }
- const want = unix.EHOSTUNREACH
- switch got := unix.Errno(dut.GetSockOptInt(t, clientFD, unix.SOL_SOCKET, unix.SO_ERROR)); got {
- case unix.Errno(0):
- continue
- case want:
- return
- default:
- t.Fatalf("got SO_ERROR = %s, want %s", got, want)
- }
-
- }
- }()
-
- <-start
- sendICMPError(t, &conn, tcp)
-
- dut.PollOne(t, clientFD, unix.POLLHUP, time.Second)
- <-done
-
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
- // The DUT should reply with RST to our ACK as the state should have
- // transitioned to CLOSED because of handshake error.
- if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, time.Second); err != nil {
- t.Fatalf("expected RST, %s", err)
- }
-}
diff --git a/test/packetimpact/tests/tcp_cork_mss_test.go b/test/packetimpact/tests/tcp_cork_mss_test.go
deleted file mode 100644
index 1db3c9883..000000000
--- a/test/packetimpact/tests/tcp_cork_mss_test.go
+++ /dev/null
@@ -1,83 +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_cork_mss_test
-
-import (
- "flag"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-// TestTCPCorkMSS tests for segment coalesce and split as per MSS.
-func TestTCPCorkMSS(t *testing.T) {
- dut := testbench.NewDUT(t)
- listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(t, listenFD)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- defer conn.Close(t)
-
- const mss = uint32(header.TCPDefaultMSS)
- options := make([]byte, header.TCPOptionMSSLength)
- header.EncodeMSSOption(mss, options)
- conn.ConnectWithOptions(t, options)
-
- acceptFD, _ := dut.Accept(t, listenFD)
- defer dut.Close(t, acceptFD)
-
- dut.SetSockOptInt(t, acceptFD, unix.IPPROTO_TCP, unix.TCP_CORK, 1)
-
- // Let the dut application send 2 small segments to be held up and coalesced
- // until the application sends a larger segment to fill up to > MSS.
- sampleData := []byte("Sample Data")
- dut.Send(t, acceptFD, sampleData, 0)
- dut.Send(t, acceptFD, sampleData, 0)
-
- expectedData := sampleData
- expectedData = append(expectedData, sampleData...)
- largeData := make([]byte, mss+1)
- expectedData = append(expectedData, largeData...)
- dut.Send(t, acceptFD, largeData, 0)
-
- // Expect the segments to be coalesced and sent and capped to MSS.
- expectedPayload := testbench.Payload{Bytes: expectedData[:mss]}
- if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, &expectedPayload, time.Second); err != nil {
- t.Fatalf("expected payload was not received: %s", err)
- }
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
- // Expect the coalesced segment to be split and transmitted.
- expectedPayload = testbench.Payload{Bytes: expectedData[mss:]}
- if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, &expectedPayload, time.Second); err != nil {
- t.Fatalf("expected payload was not received: %s", err)
- }
-
- // Check for segments to *not* be held up because of TCP_CORK when
- // the current send window is less than MSS.
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), WindowSize: testbench.Uint16(uint16(2 * len(sampleData)))})
- dut.Send(t, acceptFD, sampleData, 0)
- dut.Send(t, acceptFD, sampleData, 0)
- expectedPayload = testbench.Payload{Bytes: append(sampleData, sampleData...)}
- if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, &expectedPayload, time.Second); err != nil {
- t.Fatalf("expected payload was not received: %s", err)
- }
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
-}
diff --git a/test/packetimpact/tests/tcp_fin_retransmission_test.go b/test/packetimpact/tests/tcp_fin_retransmission_test.go
deleted file mode 100644
index 500f7a783..000000000
--- a/test/packetimpact/tests/tcp_fin_retransmission_test.go
+++ /dev/null
@@ -1,87 +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_fin_retransmission_test
-
-import (
- "flag"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-// TestTCPClosingFinRetransmission tests that TCP implementation should retransmit
-// FIN segment in CLOSING state.
-func TestTCPClosingFinRetransmission(t *testing.T) {
- for _, tt := range []struct {
- description string
- flags header.TCPFlags
- }{
- {"CLOSING", header.TCPFlagAck | header.TCPFlagFin},
- {"FIN_WAIT_1", header.TCPFlagAck},
- } {
- t.Run(tt.description, func(t *testing.T) {
- dut := testbench.NewDUT(t)
- listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(t, listenFD)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- defer conn.Close(t)
- conn.Connect(t)
- acceptFD, _ := dut.Accept(t, listenFD)
- defer dut.Close(t, acceptFD)
-
- // Give a chance for the dut to estimate RTO with RTT from the DATA-ACK.
- // TODO(gvisor.dev/issue/2685) Estimate RTO during handshake, after which
- // we can skip the next block of code.
- sampleData := []byte("Sample Data")
- if got, want := dut.Send(t, acceptFD, sampleData, 0), len(sampleData); int(got) != want {
- t.Fatalf("got dut.Send(t, %d, %s, 0) = %d, want %d", acceptFD, sampleData, got, want)
- }
- if _, err := conn.ExpectData(t, &testbench.TCP{}, &testbench.Payload{Bytes: sampleData}, time.Second); err != nil {
- t.Fatalf("expected payload was not received: %s", err)
- }
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
-
- dut.Shutdown(t, acceptFD, unix.SHUT_WR)
-
- if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil {
- t.Fatalf("expected FINACK from DUT, but got none: %s", err)
- }
-
- // Do not ack the FIN from DUT so that we can test for retransmission.
- seqNumForTheirFIN := testbench.Uint32(uint32(*conn.RemoteSeqNum(t)) - 1)
- conn.Send(t, testbench.TCP{AckNum: seqNumForTheirFIN, Flags: testbench.TCPFlags(tt.flags)})
-
- if tt.flags&header.TCPFlagFin != 0 {
- if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil {
- t.Errorf("expected an ACK to our FIN, but got none: %s", err)
- }
- }
-
- if _, err := conn.Expect(t, testbench.TCP{
- SeqNum: seqNumForTheirFIN,
- Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck),
- }, time.Second); err != nil {
- t.Errorf("expected retransmission of FIN from the DUT: %s", err)
- }
- })
- }
-}
diff --git a/test/packetimpact/tests/tcp_handshake_window_size_test.go b/test/packetimpact/tests/tcp_handshake_window_size_test.go
deleted file mode 100644
index 668e0275c..000000000
--- a/test/packetimpact/tests/tcp_handshake_window_size_test.go
+++ /dev/null
@@ -1,65 +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_handshake_window_size_test
-
-import (
- "flag"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-// TestTCPHandshakeWindowSize tests if the stack is honoring the window size
-// communicated during handshake.
-func TestTCPHandshakeWindowSize(t *testing.T) {
- dut := testbench.NewDUT(t)
- listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(t, listenFD)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- defer conn.Close(t)
-
- // Start handshake with zero window size.
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn), WindowSize: testbench.Uint16(uint16(0))})
- if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err != nil {
- t.Fatalf("expected SYN-ACK: %s", err)
- }
- // Update the advertised window size to a non-zero value with the ACK that
- // completes the handshake.
- //
- // Set the window size with MSB set and expect the dut to treat it as
- // an unsigned value.
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), WindowSize: testbench.Uint16(uint16(1 << 15))})
-
- acceptFd, _ := dut.Accept(t, listenFD)
- defer dut.Close(t, acceptFd)
-
- sampleData := []byte("Sample Data")
- samplePayload := &testbench.Payload{Bytes: sampleData}
-
- // Since we advertised a zero window followed by a non-zero window,
- // expect the dut to honor the recently advertised non-zero window
- // and actually send out the data instead of probing for zero window.
- dut.Send(t, acceptFd, sampleData, 0)
- if _, err := conn.ExpectNextData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload, time.Second); err != nil {
- t.Fatalf("expected payload was not received: %s", err)
- }
-}
diff --git a/test/packetimpact/tests/tcp_info_test.go b/test/packetimpact/tests/tcp_info_test.go
deleted file mode 100644
index 5410cc368..000000000
--- a/test/packetimpact/tests/tcp_info_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_info_test
-
-import (
- "flag"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-func TestTCPInfo(t *testing.T) {
- // Create a socket, listen, TCP connect, and accept.
- dut := testbench.NewDUT(t)
- listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(t, listenFD)
-
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- defer conn.Close(t)
- conn.Connect(t)
-
- acceptFD, _ := dut.Accept(t, listenFD)
- defer dut.Close(t, acceptFD)
-
- // Send and receive sample data.
- sampleData := []byte("Sample Data")
- samplePayload := &testbench.Payload{Bytes: sampleData}
- dut.Send(t, acceptFD, sampleData, 0)
- if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil {
- t.Fatalf("expected a packet with payload %s: %s", samplePayload, err)
- }
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
-
- info := dut.GetSockOptTCPInfo(t, acceptFD)
- if got, want := uint32(info.State), linux.TCP_ESTABLISHED; got != want {
- t.Fatalf("got %d want %d", got, want)
- }
- if info.RTT == 0 {
- t.Errorf("got RTT=0, want nonzero")
- }
- if info.RTTVar == 0 {
- t.Errorf("got RTTVar=0, want nonzero")
- }
- if info.RTO == 0 {
- t.Errorf("got RTO=0, want nonzero")
- }
- if info.ReordSeen != 0 {
- t.Errorf("expected the connection to not have any reordering, got: %d want: 0", info.ReordSeen)
- }
- if info.SndCwnd == 0 {
- t.Errorf("expected send congestion window to be greater than zero")
- }
- if info.CaState != linux.TCP_CA_Open {
- t.Errorf("expected the connection to be in open state, got: %d want: %d", info.CaState, linux.TCP_CA_Open)
- }
-
- if t.Failed() {
- t.FailNow()
- }
-
- // Check the congestion control state and send congestion window after
- // retransmission timeout.
- seq := testbench.Uint32(uint32(*conn.RemoteSeqNum(t)))
- dut.Send(t, acceptFD, sampleData, 0)
- if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil {
- t.Fatalf("expected a packet with payload %s: %s", samplePayload, err)
- }
-
- // Given a generous retransmission timeout.
- timeout := time.Duration(info.RTO) * 2 * time.Microsecond
- if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: seq}, samplePayload, timeout); err != nil {
- t.Fatalf("expected a packet with payload %s: %s", samplePayload, err)
- }
-
- info = dut.GetSockOptTCPInfo(t, acceptFD)
- if info.CaState != linux.TCP_CA_Loss {
- t.Errorf("expected the connection to be in loss recovery, got: %d want: %d", info.CaState, linux.TCP_CA_Loss)
- }
- if info.SndCwnd != 1 {
- t.Errorf("expected send congestion window to be 1, got: %d", info.SndCwnd)
- }
-}
diff --git a/test/packetimpact/tests/tcp_linger_test.go b/test/packetimpact/tests/tcp_linger_test.go
deleted file mode 100644
index 46b5ca5d8..000000000
--- a/test/packetimpact/tests/tcp_linger_test.go
+++ /dev/null
@@ -1,265 +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_linger_test
-
-import (
- "context"
- "flag"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-func createSocket(t *testing.T, dut testbench.DUT) (int32, int32, testbench.TCPIPv4) {
- listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- conn.Connect(t)
- acceptFD, _ := dut.Accept(t, listenFD)
- return acceptFD, listenFD, conn
-}
-
-func closeAll(t *testing.T, dut testbench.DUT, listenFD int32, conn testbench.TCPIPv4) {
- conn.Close(t)
- dut.Close(t, listenFD)
-}
-
-// lingerDuration is the timeout value used with SO_LINGER socket option.
-const lingerDuration = 3 * time.Second
-
-// TestTCPLingerZeroTimeout tests when SO_LINGER is set with zero timeout. DUT
-// should send RST-ACK when socket is closed.
-func TestTCPLingerZeroTimeout(t *testing.T) {
- // Create a socket, listen, TCP connect, and accept.
- dut := testbench.NewDUT(t)
- acceptFD, listenFD, conn := createSocket(t, dut)
- defer closeAll(t, dut, listenFD, conn)
-
- dut.SetSockLingerOption(t, acceptFD, 0, true)
- dut.Close(t, acceptFD)
-
- // If the linger timeout is set to zero, the DUT should send a RST.
- if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst | header.TCPFlagAck)}, time.Second); err != nil {
- t.Errorf("expected RST-ACK packet within a second but got none: %s", err)
- }
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
-}
-
-// TestTCPLingerOff tests when SO_LINGER is not set. DUT should send FIN-ACK
-// when socket is closed.
-func TestTCPLingerOff(t *testing.T) {
- // Create a socket, listen, TCP connect, and accept.
- dut := testbench.NewDUT(t)
- acceptFD, listenFD, conn := createSocket(t, dut)
- defer closeAll(t, dut, listenFD, conn)
-
- dut.Close(t, acceptFD)
-
- // If SO_LINGER is not set, DUT should send a FIN-ACK.
- if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil {
- t.Errorf("expected FIN-ACK packet within a second but got none: %s", err)
- }
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
-}
-
-// TestTCPLingerNonZeroTimeout tests when SO_LINGER is set with non-zero timeout.
-// DUT should close the socket after timeout.
-func TestTCPLingerNonZeroTimeout(t *testing.T) {
- for _, tt := range []struct {
- description string
- lingerOn bool
- }{
- {"WithNonZeroLinger", true},
- {"WithoutLinger", false},
- } {
- t.Run(tt.description, func(t *testing.T) {
- // Create a socket, listen, TCP connect, and accept.
- dut := testbench.NewDUT(t)
- acceptFD, listenFD, conn := createSocket(t, dut)
- defer closeAll(t, dut, listenFD, conn)
-
- dut.SetSockLingerOption(t, acceptFD, lingerDuration, tt.lingerOn)
-
- start := time.Now()
- dut.CloseWithErrno(context.Background(), t, acceptFD)
- elapsed := time.Since(start)
-
- expectedMaximum := time.Second
- if tt.lingerOn {
- expectedMaximum += lingerDuration
- if elapsed < lingerDuration {
- t.Errorf("expected close to take at least %s, but took %s", lingerDuration, elapsed)
- }
- }
- if elapsed >= expectedMaximum {
- t.Errorf("expected close to take at most %s, but took %s", expectedMaximum, elapsed)
- }
-
- if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil {
- t.Errorf("expected FIN-ACK packet within a second but got none: %s", err)
- }
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
- })
- }
-}
-
-// TestTCPLingerSendNonZeroTimeout tests when SO_LINGER is set with non-zero
-// timeout and send a packet. DUT should close the socket after timeout.
-func TestTCPLingerSendNonZeroTimeout(t *testing.T) {
- for _, tt := range []struct {
- description string
- lingerOn bool
- }{
- {"WithSendNonZeroLinger", true},
- {"WithoutLinger", false},
- } {
- t.Run(tt.description, func(t *testing.T) {
- // Create a socket, listen, TCP connect, and accept.
- dut := testbench.NewDUT(t)
- acceptFD, listenFD, conn := createSocket(t, dut)
- defer closeAll(t, dut, listenFD, conn)
-
- dut.SetSockLingerOption(t, acceptFD, lingerDuration, tt.lingerOn)
-
- // Send data.
- sampleData := []byte("Sample Data")
- dut.Send(t, acceptFD, sampleData, 0)
-
- start := time.Now()
- dut.CloseWithErrno(context.Background(), t, acceptFD)
- elapsed := time.Since(start)
-
- expectedMaximum := time.Second
- if tt.lingerOn {
- expectedMaximum += lingerDuration
- if elapsed < lingerDuration {
- t.Errorf("expected close to take at least %s, but took %s", lingerDuration, elapsed)
- }
- }
- if elapsed >= expectedMaximum {
- t.Errorf("expected close to take at most %s, but took %s", expectedMaximum, elapsed)
- }
-
- samplePayload := &testbench.Payload{Bytes: sampleData}
- if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil {
- t.Fatalf("expected a packet with payload %v: %s", samplePayload, err)
- }
-
- if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil {
- t.Errorf("expected FIN-ACK packet within a second but got none: %s", err)
- }
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
- })
- }
-}
-
-// TestTCPLingerShutdownZeroTimeout tests SO_LINGER with shutdown() and zero
-// timeout. DUT should send RST-ACK when socket is closed.
-func TestTCPLingerShutdownZeroTimeout(t *testing.T) {
- // Create a socket, listen, TCP connect, and accept.
- dut := testbench.NewDUT(t)
- acceptFD, listenFD, conn := createSocket(t, dut)
- defer closeAll(t, dut, listenFD, conn)
-
- dut.SetSockLingerOption(t, acceptFD, 0, true)
- dut.Shutdown(t, acceptFD, unix.SHUT_RDWR)
- dut.Close(t, acceptFD)
-
- // Shutdown will send FIN-ACK with read/write option.
- if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil {
- t.Errorf("expected FIN-ACK packet within a second but got none: %s", err)
- }
-
- // If the linger timeout is set to zero, the DUT should send a RST.
- if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst | header.TCPFlagAck)}, time.Second); err != nil {
- t.Errorf("expected RST-ACK packet within a second but got none: %s", err)
- }
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
-}
-
-// TestTCPLingerShutdownSendNonZeroTimeout tests SO_LINGER with shutdown() and
-// non-zero timeout. DUT should close the socket after timeout.
-func TestTCPLingerShutdownSendNonZeroTimeout(t *testing.T) {
- for _, tt := range []struct {
- description string
- lingerOn bool
- }{
- {"shutdownRDWR", true},
- {"shutdownRDWR", false},
- } {
- t.Run(tt.description, func(t *testing.T) {
- // Create a socket, listen, TCP connect, and accept.
- dut := testbench.NewDUT(t)
- acceptFD, listenFD, conn := createSocket(t, dut)
- defer closeAll(t, dut, listenFD, conn)
-
- dut.SetSockLingerOption(t, acceptFD, lingerDuration, tt.lingerOn)
-
- // Send data.
- sampleData := []byte("Sample Data")
- dut.Send(t, acceptFD, sampleData, 0)
-
- dut.Shutdown(t, acceptFD, unix.SHUT_RDWR)
-
- start := time.Now()
- dut.CloseWithErrno(context.Background(), t, acceptFD)
- elapsed := time.Since(start)
-
- expectedMaximum := time.Second
- if tt.lingerOn {
- expectedMaximum += lingerDuration
- if elapsed < lingerDuration {
- t.Errorf("expected close to take at least %s, but took %s", lingerDuration, elapsed)
- }
- }
- if elapsed >= expectedMaximum {
- t.Errorf("expected close to take at most %s, but took %s", expectedMaximum, elapsed)
- }
-
- samplePayload := &testbench.Payload{Bytes: sampleData}
- if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil {
- t.Fatalf("expected a packet with payload %v: %s", samplePayload, err)
- }
-
- if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil {
- t.Errorf("expected FIN-ACK packet within a second but got none: %s", err)
- }
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
- })
- }
-}
-
-func TestTCPLingerNonEstablished(t *testing.T) {
- dut := testbench.NewDUT(t)
- newFD := dut.Socket(t, unix.AF_INET, unix.SOCK_STREAM, unix.IPPROTO_TCP)
- dut.SetSockLingerOption(t, newFD, lingerDuration, true)
-
- // As the socket is in the initial state, Close() should not linger
- // and return immediately.
- start := time.Now()
- dut.CloseWithErrno(context.Background(), t, newFD)
- elapsed := time.Since(start)
-
- expectedMaximum := time.Second
- if elapsed >= time.Second {
- t.Errorf("expected close to take at most %s, but took %s", expectedMaximum, elapsed)
- }
-}
diff --git a/test/packetimpact/tests/tcp_listen_backlog_test.go b/test/packetimpact/tests/tcp_listen_backlog_test.go
deleted file mode 100644
index fea7d5b6f..000000000
--- a/test/packetimpact/tests/tcp_listen_backlog_test.go
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright 2021 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_listen_backlog_test
-
-import (
- "flag"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-// TestTCPListenBacklog tests for a listening endpoint behavior:
-// (1) reply to more SYNs than what is configured as listen backlog
-// (2) ignore ACKs (that complete a handshake) when the accept queue is full
-// (3) ignore incoming SYNs when the accept queue is full
-func TestTCPListenBacklog(t *testing.T) {
- dut := testbench.NewDUT(t)
-
- // Listening endpoint accepts one more connection than the listen backlog.
- listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 0 /*backlog*/)
-
- var establishedConn testbench.TCPIPv4
- var incompleteConn testbench.TCPIPv4
-
- // Test if the DUT listener replies to more SYNs than listen backlog+1
- for i, conn := range []*testbench.TCPIPv4{&establishedConn, &incompleteConn} {
- *conn = dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- // Expect dut connection to have transitioned to SYN-RCVD state.
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)})
- if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err != nil {
- t.Fatalf("expected SYN-ACK for %d connection, %s", i, err)
- }
- }
- defer establishedConn.Close(t)
- defer incompleteConn.Close(t)
-
- // Send the ACK to complete handshake.
- establishedConn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
-
- // Poll for the established connection ready for accept.
- dut.PollOne(t, listenFd, unix.POLLIN, time.Second)
-
- // Send the ACK to complete handshake, expect this to be dropped by the
- // listener as the accept queue would be full because of the previous
- // handshake.
- incompleteConn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
- // Let the test wait for sometime so that the ACK is indeed dropped by
- // the listener. Without such a wait, the DUT accept can race with
- // ACK handling (dropping) causing the test to be flaky.
- time.Sleep(100 * time.Millisecond)
-
- // Drain the accept queue to enable poll for subsequent connections on the
- // listener.
- fd, _ := dut.Accept(t, listenFd)
- dut.Close(t, fd)
-
- // The ACK for the incomplete connection should be ignored by the
- // listening endpoint and the poll on listener should now time out.
- if pfds := dut.Poll(t, []unix.PollFd{{Fd: listenFd, Events: unix.POLLIN}}, time.Second); len(pfds) != 0 {
- t.Fatalf("got dut.Poll(...) = %#v", pfds)
- }
-
- // Re-send the ACK to complete handshake and re-fill the accept-queue.
- incompleteConn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
- dut.PollOne(t, listenFd, unix.POLLIN, time.Second)
-
- // Now initiate a new connection when the accept queue is full.
- connectingConn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- defer connectingConn.Close(t)
- // Expect dut connection to drop the SYN and let the client stay in SYN_SENT state.
- connectingConn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)})
- if got, err := connectingConn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err == nil {
- t.Fatalf("expected no SYN-ACK, but got %s", got)
- }
-}
diff --git a/test/packetimpact/tests/tcp_network_unreachable_test.go b/test/packetimpact/tests/tcp_network_unreachable_test.go
deleted file mode 100644
index e92e6aa9b..000000000
--- a/test/packetimpact/tests/tcp_network_unreachable_test.go
+++ /dev/null
@@ -1,144 +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_synsent_reset_test
-
-import (
- "context"
- "flag"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-// TestTCPSynSentUnreachable verifies that TCP connections fail immediately when
-// an ICMP destination unreachable message is sent in response to the inital
-// SYN.
-func TestTCPSynSentUnreachable(t *testing.T) {
- // Create the DUT and connection.
- dut := testbench.NewDUT(t)
- clientFD, clientPort := dut.CreateBoundSocket(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, dut.Net.RemoteIPv4)
- port := uint16(9001)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{SrcPort: &port, DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort, DstPort: &port})
- defer conn.Close(t)
-
- // Bring the DUT to SYN-SENT state with a non-blocking connect.
- sa := unix.SockaddrInet4{Port: int(port)}
- copy(sa.Addr[:], dut.Net.LocalIPv4)
- if _, err := dut.ConnectWithErrno(context.Background(), t, clientFD, &sa); err != unix.EINPROGRESS {
- t.Errorf("got connect() = %v, want EINPROGRESS", err)
- }
-
- // Get the SYN.
- tcp, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, time.Second)
- if err != nil {
- t.Fatalf("expected SYN: %s", err)
- }
-
- // Send a host unreachable message.
- icmpPayload := testbench.Layers{tcp.Prev(), tcp}
- bytes, err := icmpPayload.ToBytes()
- if err != nil {
- t.Fatalf("got icmpPayload.ToBytes() = (_, %s), want = (_, nil)", err)
- }
-
- layers := conn.CreateFrame(t, nil)
- layers[len(layers)-1] = &testbench.ICMPv4{
- Type: testbench.ICMPv4Type(header.ICMPv4DstUnreachable),
- Code: testbench.ICMPv4Code(header.ICMPv4HostUnreachable),
- Payload: bytes,
- }
- conn.SendFrameStateless(t, layers)
-
- if err := getConnectError(t, &dut, clientFD); err != unix.EHOSTUNREACH {
- t.Errorf("got connect() = %v, want EHOSTUNREACH", err)
- }
-}
-
-// TestTCPSynSentUnreachable6 verifies that TCP connections fail immediately when
-// an ICMP destination unreachable message is sent in response to the inital
-// SYN.
-func TestTCPSynSentUnreachable6(t *testing.T) {
- // Create the DUT and connection.
- dut := testbench.NewDUT(t)
- clientFD, clientPort := dut.CreateBoundSocket(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, dut.Net.RemoteIPv6)
- conn := dut.Net.NewTCPIPv6(t, testbench.TCP{DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort})
- defer conn.Close(t)
-
- // Bring the DUT to SYN-SENT state with a non-blocking connect.
- sa := unix.SockaddrInet6{
- Port: int(conn.SrcPort()),
- ZoneId: dut.Net.RemoteDevID,
- }
- copy(sa.Addr[:], dut.Net.LocalIPv6)
- if _, err := dut.ConnectWithErrno(context.Background(), t, clientFD, &sa); err != unix.EINPROGRESS {
- t.Errorf("got connect() = %v, want EINPROGRESS", err)
- }
-
- // Get the SYN.
- tcp, err := conn.Expect(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, time.Second)
- if err != nil {
- t.Fatalf("expected SYN: %s", err)
- }
-
- // Send a host unreachable message.
- icmpPayload := testbench.Layers{tcp.Prev(), tcp}
- bytes, err := icmpPayload.ToBytes()
- if err != nil {
- t.Fatalf("got icmpPayload.ToBytes() = (_, %s), want = (_, nil)", err)
- }
-
- layers := conn.CreateFrame(t, nil)
- layers[len(layers)-1] = &testbench.ICMPv6{
- Type: testbench.ICMPv6Type(header.ICMPv6DstUnreachable),
- Code: testbench.ICMPv6Code(header.ICMPv6NetworkUnreachable),
- Payload: bytes,
- }
- conn.SendFrameStateless(t, layers)
-
- if err := getConnectError(t, &dut, clientFD); err != unix.ENETUNREACH {
- t.Errorf("got connect() = %v, want EHOSTUNREACH", err)
- }
-}
-
-// getConnectError gets the errno generated by the on-going connect attempt on
-// fd. fd must be non-blocking and there must be a connect call to fd which
-// returned EINPROGRESS before. These conditions are guaranteed in this test.
-func getConnectError(t *testing.T, dut *testbench.DUT, fd int32) error {
- t.Helper()
- // We previously got EINPROGRESS form the connect call. We can
- // handle it as explained by connect(2):
- // EINPROGRESS:
- // The socket is nonblocking and the connection cannot be
- // completed immediately. It is possible to select(2) or poll(2)
- // for completion by selecting the socket for writing. After
- // select(2) indicates writability, use getsockopt(2) to read
- // the SO_ERROR option at level SOL_SOCKET to determine
- // whether connect() completed successfully (SO_ERROR is
- // zero) or unsuccessfully (SO_ERROR is one of the usual
- // error codes listed here, explaining the reason for the
- // failure).
- dut.PollOne(t, fd, unix.POLLOUT, 10*time.Second)
- if errno := dut.GetSockOptInt(t, fd, unix.SOL_SOCKET, unix.SO_ERROR); errno != 0 {
- return unix.Errno(errno)
- }
- return nil
-}
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 14eb7d93b..000000000
--- a/test/packetimpact/tests/tcp_noaccept_close_rst_test.go
+++ /dev/null
@@ -1,46 +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 (
- "flag"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-func TestTcpNoAcceptCloseReset(t *testing.T) {
- dut := testbench.NewDUT(t)
- listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- conn.Connect(t)
- defer conn.Close(t)
- // We need to wait for POLLIN event on listenFd to know the connection is
- // established. Otherwise there could be a race when we issue the Close
- // command prior to the DUT receiving the last ack of the handshake and
- // it will only respond RST instead of RST+ACK.
- dut.PollOne(t, listenFd, unix.POLLIN, time.Second)
- dut.Close(t, listenFd)
- if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(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 0523887d9..000000000
--- a/test/packetimpact/tests/tcp_outside_the_window_test.go
+++ /dev/null
@@ -1,182 +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 (
- "flag"
- "fmt"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/seqnum"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-// 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 header.TCPFlags
- payload []testbench.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, []testbench.Layer{&testbench.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, []testbench.Layer{&testbench.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, []testbench.Layer{&testbench.Payload{Bytes: []byte("abc123")}}, 2, true},
- } {
- t.Run(fmt.Sprintf("%s%d", tt.description, tt.seqNumOffset), func(t *testing.T) {
- dut := testbench.NewDUT(t)
- listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(t, listenFD)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- defer conn.Close(t)
- conn.Connect(t)
- acceptFD, _ := dut.Accept(t, listenFD)
- defer dut.Close(t, acceptFD)
-
- windowSize := seqnum.Size(*conn.SynAck(t).WindowSize) + tt.seqNumOffset
- conn.Drain(t)
- // Ignore whatever incrementing that this out-of-order packet might cause
- // to the AckNum.
- localSeqNum := testbench.Uint32(uint32(*conn.LocalSeqNum(t)))
- conn.Send(t, testbench.TCP{
- Flags: testbench.TCPFlags(tt.tcpFlags),
- SeqNum: testbench.Uint32(uint32(conn.LocalSeqNum(t).Add(windowSize))),
- }, tt.payload...)
- timeout := time.Second
- gotACK, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: localSeqNum}, timeout)
- if tt.expectACK && err != nil {
- t.Fatalf("expected an ACK packet within %s but got none: %s", timeout, err)
- }
- // Data packets w/o SYN bits are always acked by Linux. Netstack ACK's data packets
- // always right now. So only send a second segment and test for no ACK for packets
- // with no data.
- if tt.expectACK && tt.payload == nil {
- // Sending another out-of-window segment immediately should not trigger
- // an ACK if less than 500ms(default rate limit for out-of-window ACKs)
- // has passed since the last ACK was sent.
- t.Logf("sending another segment")
- conn.Send(t, testbench.TCP{
- Flags: testbench.TCPFlags(tt.tcpFlags),
- SeqNum: testbench.Uint32(uint32(conn.LocalSeqNum(t).Add(windowSize))),
- }, tt.payload...)
- timeout := 3 * time.Second
- gotACK, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: localSeqNum}, timeout)
- if err == nil {
- t.Fatalf("expected no ACK packet but got one: %s", gotACK)
- }
- }
- if !tt.expectACK && gotACK != nil {
- t.Fatalf("expected no ACK packet within %s but got one: %s", timeout, gotACK)
- }
- })
- }
-}
-
-// TestAckOTWSeqInClosing tests that the DUT should send an ACK with
-// the right ACK number when receiving a packet with OTW Seq number
-// in CLOSING state. https://tools.ietf.org/html/rfc793#page-69
-func TestAckOTWSeqInClosing(t *testing.T) {
- for _, tt := range []struct {
- description string
- flags header.TCPFlags
- payloads testbench.Layers
- 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},
- {"FINACK", header.TCPFlagFin | header.TCPFlagAck, nil, 0, false},
- {"Data", header.TCPFlagAck, []testbench.Layer{&testbench.Payload{Bytes: []byte("Sample Data")}}, 0, false},
-
- {"SYN", header.TCPFlagSyn, nil, 1, true},
- {"SYNACK", header.TCPFlagSyn | header.TCPFlagAck, nil, 1, true},
- {"ACK", header.TCPFlagAck, nil, 1, true},
- {"FINACK", header.TCPFlagFin | header.TCPFlagAck, nil, 1, true},
- {"Data", header.TCPFlagAck, []testbench.Layer{&testbench.Payload{Bytes: []byte("Sample Data")}}, 1, true},
-
- {"SYN", header.TCPFlagSyn, nil, 2, true},
- {"SYNACK", header.TCPFlagSyn | header.TCPFlagAck, nil, 2, true},
- {"ACK", header.TCPFlagAck, nil, 2, true},
- {"FINACK", header.TCPFlagFin | header.TCPFlagAck, nil, 2, true},
- {"Data", header.TCPFlagAck, []testbench.Layer{&testbench.Payload{Bytes: []byte("Sample Data")}}, 2, true},
- } {
- t.Run(fmt.Sprintf("%s%d", tt.description, tt.seqNumOffset), func(t *testing.T) {
- dut := testbench.NewDUT(t)
- listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(t, listenFD)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- defer conn.Close(t)
- conn.Connect(t)
- acceptFD, _ := dut.Accept(t, listenFD)
- defer dut.Close(t, acceptFD)
-
- dut.Shutdown(t, acceptFD, unix.SHUT_WR)
-
- if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil {
- t.Fatalf("expected FINACK from DUT, but got none: %s", err)
- }
-
- // Do not ack the FIN from DUT so that the TCP state on DUT is CLOSING instead of CLOSED.
- seqNumForTheirFIN := testbench.Uint32(uint32(*conn.RemoteSeqNum(t)) - 1)
- conn.Send(t, testbench.TCP{AckNum: seqNumForTheirFIN, Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)})
-
- gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second)
- if err != nil {
- t.Fatalf("expected an ACK to our FIN, but got none: %s", err)
- }
-
- windowSize := seqnum.Size(*gotTCP.WindowSize) + tt.seqNumOffset
- conn.SendFrameStateless(t, conn.CreateFrame(t, testbench.Layers{&testbench.TCP{
- SeqNum: testbench.Uint32(uint32(conn.LocalSeqNum(t).Add(windowSize))),
- AckNum: seqNumForTheirFIN,
- Flags: testbench.TCPFlags(tt.flags),
- }}, tt.payloads...))
-
- gotACK, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second)
- if tt.expectACK && err != nil {
- t.Errorf("expected an ACK but got none: %s", err)
- }
- if !tt.expectACK && gotACK != nil {
- t.Errorf("expected no ACK but got one: %s", gotACK)
- }
- })
- }
-}
diff --git a/test/packetimpact/tests/tcp_paws_mechanism_test.go b/test/packetimpact/tests/tcp_paws_mechanism_test.go
deleted file mode 100644
index 9054955ea..000000000
--- a/test/packetimpact/tests/tcp_paws_mechanism_test.go
+++ /dev/null
@@ -1,108 +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_paws_mechanism_test
-
-import (
- "encoding/hex"
- "flag"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-func TestPAWSMechanism(t *testing.T) {
- dut := testbench.NewDUT(t)
- listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(t, listenFD)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- defer conn.Close(t)
-
- options := make([]byte, header.TCPOptionTSLength)
- header.EncodeTSOption(currentTS(), 0, options)
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn), Options: options})
- synAck, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, time.Second)
- if err != nil {
- t.Fatalf("didn't get synack during handshake: %s", err)
- }
- parsedSynOpts := header.ParseSynOptions(synAck.Options, true)
- if !parsedSynOpts.TS {
- t.Fatalf("expected TSOpt from DUT, options we got:\n%s", hex.Dump(synAck.Options))
- }
- tsecr := parsedSynOpts.TSVal
- header.EncodeTSOption(currentTS(), tsecr, options)
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), Options: options})
- acceptFD, _ := dut.Accept(t, listenFD)
- defer dut.Close(t, acceptFD)
-
- sampleData := []byte("Sample Data")
- sentTSVal := currentTS()
- header.EncodeTSOption(sentTSVal, tsecr, options)
- // 3ms here is chosen arbitrarily to make sure we have increasing timestamps
- // every time we send one, it should not cause any flakiness because timestamps
- // only need to be non-decreasing.
- time.Sleep(3 * time.Millisecond)
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), Options: options}, &testbench.Payload{Bytes: sampleData})
-
- gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second)
- if err != nil {
- t.Fatalf("expected an ACK but got none: %s", err)
- }
-
- parsedOpts := header.ParseTCPOptions(gotTCP.Options)
- if !parsedOpts.TS {
- t.Fatalf("expected TS option in response, options we got:\n%s", hex.Dump(gotTCP.Options))
- }
- if parsedOpts.TSVal < tsecr {
- t.Fatalf("TSVal should be non-decreasing, but %d < %d", parsedOpts.TSVal, tsecr)
- }
- if parsedOpts.TSEcr != sentTSVal {
- t.Fatalf("TSEcr should match our sent TSVal, %d != %d", parsedOpts.TSEcr, sentTSVal)
- }
- tsecr = parsedOpts.TSVal
- lastAckNum := gotTCP.AckNum
-
- badTSVal := sentTSVal - 100
- header.EncodeTSOption(badTSVal, tsecr, options)
- // 3ms here is chosen arbitrarily and this time.Sleep() should not cause flakiness
- // due to the exact same reasoning discussed above.
- time.Sleep(3 * time.Millisecond)
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), Options: options}, &testbench.Payload{Bytes: sampleData})
-
- gotTCP, err = conn.Expect(t, testbench.TCP{AckNum: lastAckNum, Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second)
- if err != nil {
- t.Fatalf("expected segment with AckNum %d but got none: %s", lastAckNum, err)
- }
- parsedOpts = header.ParseTCPOptions(gotTCP.Options)
- if !parsedOpts.TS {
- t.Fatalf("expected TS option in response, options we got:\n%s", hex.Dump(gotTCP.Options))
- }
- if parsedOpts.TSVal < tsecr {
- t.Fatalf("TSVal should be non-decreasing, but %d < %d", parsedOpts.TSVal, tsecr)
- }
- if parsedOpts.TSEcr != sentTSVal {
- t.Fatalf("TSEcr should match our sent TSVal, %d != %d", parsedOpts.TSEcr, sentTSVal)
- }
-}
-
-func currentTS() uint32 {
- return uint32(time.Now().UnixNano() / 1e6)
-}
diff --git a/test/packetimpact/tests/tcp_queue_send_recv_in_syn_sent_test.go b/test/packetimpact/tests/tcp_queue_send_recv_in_syn_sent_test.go
deleted file mode 100644
index 974c15384..000000000
--- a/test/packetimpact/tests/tcp_queue_send_recv_in_syn_sent_test.go
+++ /dev/null
@@ -1,286 +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_queue_send_recv_in_syn_sent_test
-
-import (
- "bytes"
- "context"
- "encoding/hex"
- "errors"
- "flag"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-// TestQueueSendInSynSentHandshake tests send behavior when the TCP state
-// is SYN-SENT and the connections is finally established.
-func TestQueueSendInSynSentHandshake(t *testing.T) {
- dut := testbench.NewDUT(t)
- socket, remotePort := dut.CreateBoundSocket(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, dut.Net.RemoteIPv4)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- defer conn.Close(t)
-
- sampleData := []byte("Sample Data")
-
- dut.SetNonBlocking(t, socket, true)
- if _, err := dut.ConnectWithErrno(context.Background(), t, socket, conn.LocalAddr(t)); !errors.Is(err, unix.EINPROGRESS) {
- t.Fatalf("failed to bring DUT to SYN-SENT, got: %s, want EINPROGRESS", err)
- }
- if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, time.Second); err != nil {
- t.Fatalf("expected a SYN from DUT, but got none: %s", err)
- }
-
- // Test blocking send.
- dut.SetNonBlocking(t, socket, false)
-
- start := make(chan struct{})
- done := make(chan struct{})
- go func() {
- defer close(done)
-
- close(start)
- // Issue SEND call in SYN-SENT, this should be queued for
- // process until the connection is established.
- if _, err := dut.SendWithErrno(context.Background(), t, socket, sampleData, 0); err != unix.Errno(0) {
- t.Errorf("failed to send on DUT: %s", err)
- }
- }()
-
- // Wait for the goroutine to be scheduled and before it
- // blocks on endpoint send/receive.
- <-start
- // The following sleep is used to prevent the connection
- // from being established before we are blocked: there is
- // still a small time window between we sending the RPC
- // request and the system actually being blocked.
- time.Sleep(100 * time.Millisecond)
-
- select {
- case <-done:
- t.Fatal("expected send to be blocked in SYN-SENT")
- default:
- }
-
- // Bring the connection to Established.
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)})
-
- <-done
-
- // Expect the data from the DUT's enqueued send request.
- //
- // On Linux, this can be piggybacked with the ACK completing the
- // handshake. On gVisor, getting such a piggyback is a bit more
- // complicated because the actual data enqueuing occurs in the
- // callers of endpoint Write.
- if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagPsh | header.TCPFlagAck)}, &testbench.Payload{Bytes: sampleData}, time.Second); err != nil {
- t.Fatalf("expected payload was not received: %s", err)
- }
-
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagPsh | header.TCPFlagAck)}, &testbench.Payload{Bytes: sampleData})
- if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil {
- t.Fatalf("expected an ACK from DUT, but got none: %s", err)
- }
-}
-
-// TestQueueRecvInSynSentHandshake tests recv behavior when the TCP state
-// is SYN-SENT and the connections is finally established.
-func TestQueueRecvInSynSentHandshake(t *testing.T) {
- dut := testbench.NewDUT(t)
- socket, remotePort := dut.CreateBoundSocket(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, dut.Net.RemoteIPv4)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- defer conn.Close(t)
-
- sampleData := []byte("Sample Data")
-
- dut.SetNonBlocking(t, socket, true)
- if _, err := dut.ConnectWithErrno(context.Background(), t, socket, conn.LocalAddr(t)); !errors.Is(err, unix.EINPROGRESS) {
- t.Fatalf("failed to bring DUT to SYN-SENT, got: %s, want EINPROGRESS", err)
- }
- if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, time.Second); err != nil {
- t.Fatalf("expected a SYN from DUT, but got none: %s", err)
- }
-
- if _, _, err := dut.RecvWithErrno(context.Background(), t, socket, int32(len(sampleData)), 0); err != unix.EWOULDBLOCK {
- t.Fatalf("expected error %s, got %s", unix.EWOULDBLOCK, err)
- }
-
- // Test blocking read.
- dut.SetNonBlocking(t, socket, false)
-
- start := make(chan struct{})
- done := make(chan struct{})
- go func() {
- defer close(done)
-
- close(start)
- // Issue RECEIVE call in SYN-SENT, this should be queued for
- // process until the connection is established.
- n, buff, err := dut.RecvWithErrno(context.Background(), t, socket, int32(len(sampleData)), 0)
- if err != unix.Errno(0) {
- t.Errorf("failed to recv on DUT: %s", err)
- return
- }
- if got := buff[:n]; !bytes.Equal(got, sampleData) {
- t.Errorf("received data doesn't match, got:\n%s, want:\n%s", hex.Dump(got), hex.Dump(sampleData))
- }
- }()
-
- // Wait for the goroutine to be scheduled and before it
- // blocks on endpoint send/receive.
- <-start
-
- // The following sleep is used to prevent the connection
- // from being established before we are blocked: there is
- // still a small time window between we sending the RPC
- // request and the system actually being blocked.
- time.Sleep(100 * time.Millisecond)
-
- // Bring the connection to Established.
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)})
- if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil {
- t.Fatalf("expected an ACK from DUT, but got none: %s", err)
- }
-
- // Send sample payload so that DUT can recv.
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagPsh | header.TCPFlagAck)}, &testbench.Payload{Bytes: sampleData})
- if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil {
- t.Fatalf("expected an ACK from DUT, but got none: %s", err)
- }
-
- <-done
-}
-
-// TestQueueSendInSynSentRST tests send behavior when the TCP state
-// is SYN-SENT and an RST is sent.
-func TestQueueSendInSynSentRST(t *testing.T) {
- dut := testbench.NewDUT(t)
- socket, remotePort := dut.CreateBoundSocket(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, dut.Net.RemoteIPv4)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- defer conn.Close(t)
-
- sampleData := []byte("Sample Data")
-
- dut.SetNonBlocking(t, socket, true)
- if _, err := dut.ConnectWithErrno(context.Background(), t, socket, conn.LocalAddr(t)); !errors.Is(err, unix.EINPROGRESS) {
- t.Fatalf("failed to bring DUT to SYN-SENT, got: %s, want EINPROGRESS", err)
- }
- if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, time.Second); err != nil {
- t.Fatalf("expected a SYN from DUT, but got none: %s", err)
- }
-
- // Test blocking send.
- dut.SetNonBlocking(t, socket, false)
-
- start := make(chan struct{})
- done := make(chan struct{})
- go func() {
- defer close(done)
-
- close(start)
- // Issue SEND call in SYN-SENT, this should be queued for
- // process until the connection is established.
- n, err := dut.SendWithErrno(context.Background(), t, socket, sampleData, 0)
- if err != unix.ECONNREFUSED {
- t.Errorf("expected error %s, got %s", unix.ECONNREFUSED, err)
- }
- if n != -1 {
- t.Errorf("expected return value %d, got %d", -1, n)
- }
- }()
-
- // Wait for the goroutine to be scheduled and before it
- // blocks on endpoint send/receive.
- <-start
-
- // The following sleep is used to prevent the connection
- // from being established before we are blocked: there is
- // still a small time window between we sending the RPC
- // request and the system actually being blocked.
- time.Sleep(100 * time.Millisecond)
-
- select {
- case <-done:
- t.Fatal("expected send to be blocked in SYN-SENT")
- default:
- }
-
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst | header.TCPFlagAck)})
-
- <-done
-}
-
-// TestQueueRecvInSynSentRST tests recv behavior when the TCP state
-// is SYN-SENT and an RST is sent.
-func TestQueueRecvInSynSentRST(t *testing.T) {
- dut := testbench.NewDUT(t)
- socket, remotePort := dut.CreateBoundSocket(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, dut.Net.RemoteIPv4)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- defer conn.Close(t)
-
- sampleData := []byte("Sample Data")
-
- dut.SetNonBlocking(t, socket, true)
- if _, err := dut.ConnectWithErrno(context.Background(), t, socket, conn.LocalAddr(t)); !errors.Is(err, unix.EINPROGRESS) {
- t.Fatalf("failed to bring DUT to SYN-SENT, got: %s, want EINPROGRESS", err)
- }
- if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, time.Second); err != nil {
- t.Fatalf("expected a SYN from DUT, but got none: %s", err)
- }
-
- if _, _, err := dut.RecvWithErrno(context.Background(), t, socket, int32(len(sampleData)), 0); err != unix.EWOULDBLOCK {
- t.Fatalf("expected error %s, got %s", unix.EWOULDBLOCK, err)
- }
-
- // Test blocking read.
- dut.SetNonBlocking(t, socket, false)
-
- start := make(chan struct{})
- done := make(chan struct{})
- go func() {
- defer close(done)
-
- close(start)
- // Issue RECEIVE call in SYN-SENT, this should be queued for
- // process until the connection is established.
- n, _, err := dut.RecvWithErrno(context.Background(), t, socket, int32(len(sampleData)), 0)
- if err != unix.ECONNREFUSED {
- t.Errorf("expected error %s, got %s", unix.ECONNREFUSED, err)
- }
- if n != -1 {
- t.Errorf("expected return value %d, got %d", -1, n)
- }
- }()
-
- // Wait for the goroutine to be scheduled and before it
- // blocks on endpoint send/receive.
- <-start
-
- // The following sleep is used to prevent the connection
- // from being established before we are blocked: there is
- // still a small time window between we sending the RPC
- // request and the system actually being blocked.
- time.Sleep(100 * time.Millisecond)
-
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst | header.TCPFlagAck)})
- <-done
-}
diff --git a/test/packetimpact/tests/tcp_rack_test.go b/test/packetimpact/tests/tcp_rack_test.go
deleted file mode 100644
index 5a60bf712..000000000
--- a/test/packetimpact/tests/tcp_rack_test.go
+++ /dev/null
@@ -1,404 +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_rack_test
-
-import (
- "flag"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/seqnum"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-const (
- // payloadSize is the size used to send packets.
- payloadSize = header.TCPDefaultMSS
-
- // simulatedRTT is the time delay between packets sent and acked to
- // increase the RTT.
- simulatedRTT = 30 * time.Millisecond
-
- // numPktsForRTT is the number of packets sent and acked to establish
- // RTT.
- numPktsForRTT = 10
-)
-
-func createSACKConnection(t *testing.T) (testbench.DUT, testbench.TCPIPv4, int32, int32) {
- dut := testbench.NewDUT(t)
- listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
-
- // Enable SACK.
- opts := make([]byte, 40)
- optsOff := 0
- optsOff += header.EncodeNOP(opts[optsOff:])
- optsOff += header.EncodeNOP(opts[optsOff:])
- optsOff += header.EncodeSACKPermittedOption(opts[optsOff:])
-
- conn.ConnectWithOptions(t, opts[:optsOff])
- acceptFd, _ := dut.Accept(t, listenFd)
- return dut, conn, acceptFd, listenFd
-}
-
-func closeSACKConnection(t *testing.T, dut testbench.DUT, conn testbench.TCPIPv4, acceptFd, listenFd int32) {
- dut.Close(t, acceptFd)
- dut.Close(t, listenFd)
- conn.Close(t)
-}
-
-func getRTTAndRTO(t *testing.T, dut testbench.DUT, acceptFd int32) (rtt, rto time.Duration) {
- info := dut.GetSockOptTCPInfo(t, acceptFd)
- return time.Duration(info.RTT) * time.Microsecond, time.Duration(info.RTO) * time.Microsecond
-}
-
-func sendAndReceive(t *testing.T, dut testbench.DUT, conn testbench.TCPIPv4, numPkts int, acceptFd int32, sendACK bool) time.Time {
- seqNum1 := *conn.RemoteSeqNum(t)
- payload := make([]byte, payloadSize)
- var lastSent time.Time
- for i, sn := 0, seqNum1; i < numPkts; i++ {
- lastSent = time.Now()
- dut.Send(t, acceptFd, payload, 0)
- gotOne, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(sn))}, time.Second)
- if err != nil {
- t.Fatalf("Expect #%d: %s", i+1, err)
- continue
- }
- if gotOne == nil {
- t.Fatalf("#%d: expected a packet within a second but got none", i+1)
- }
- sn.UpdateForward(seqnum.Size(payloadSize))
-
- if sendACK {
- time.Sleep(simulatedRTT)
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(sn))})
- }
- }
- return lastSent
-}
-
-// TestRACKTLPAllPacketsLost tests TLP when an entire flight of data is lost.
-func TestRACKTLPAllPacketsLost(t *testing.T) {
- dut, conn, acceptFd, listenFd := createSACKConnection(t)
- seqNum1 := *conn.RemoteSeqNum(t)
-
- // Send ACK for data packets to establish RTT.
- sendAndReceive(t, dut, conn, numPktsForRTT, acceptFd, true /* sendACK */)
- seqNum1.UpdateForward(seqnum.Size(numPktsForRTT * payloadSize))
-
- // We are not sending ACK for these packets.
- const numPkts = 5
- lastSent := sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */)
-
- // Probe Timeout (PTO) should be two times RTT. Check that the last
- // packet is retransmitted after probe timeout.
- rtt, _ := getRTTAndRTO(t, dut, acceptFd)
- pto := rtt * 2
- // We expect the 5th packet (the last unacknowledged packet) to be
- // retransmitted.
- tlpProbe := testbench.Uint32(uint32(seqNum1) + uint32((numPkts-1)*payloadSize))
- if _, err := conn.Expect(t, testbench.TCP{SeqNum: tlpProbe}, time.Second); err != nil {
- t.Fatalf("expected payload was not received: %s %v %v", err, rtt, pto)
- }
- diff := time.Now().Sub(lastSent)
- if diff < pto {
- t.Fatalf("expected payload was received before the probe timeout, got: %v, want: %v", diff, pto)
- }
- closeSACKConnection(t, dut, conn, acceptFd, listenFd)
-}
-
-// TestRACKTLPLost tests TLP when there are tail losses.
-// See: https://tools.ietf.org/html/draft-ietf-tcpm-rack-08#section-7.4
-func TestRACKTLPLost(t *testing.T) {
- dut, conn, acceptFd, listenFd := createSACKConnection(t)
- seqNum1 := *conn.RemoteSeqNum(t)
-
- // Send ACK for data packets to establish RTT.
- sendAndReceive(t, dut, conn, numPktsForRTT, acceptFd, true /* sendACK */)
- seqNum1.UpdateForward(seqnum.Size(numPktsForRTT * payloadSize))
-
- // We are not sending ACK for these packets.
- const numPkts = 10
- lastSent := sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */)
-
- // Cumulative ACK for #[1-5] packets.
- ackNum := seqNum1.Add(seqnum.Size(6 * payloadSize))
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(ackNum))})
-
- // Probe Timeout (PTO) should be two times RTT. Check that the last
- // packet is retransmitted after probe timeout.
- rtt, _ := getRTTAndRTO(t, dut, acceptFd)
- pto := rtt * 2
- // We expect the 10th packet (the last unacknowledged packet) to be
- // retransmitted.
- tlpProbe := testbench.Uint32(uint32(seqNum1) + uint32((numPkts-1)*payloadSize))
- if _, err := conn.Expect(t, testbench.TCP{SeqNum: tlpProbe}, time.Second); err != nil {
- t.Fatalf("expected payload was not received: %s", err)
- }
- diff := time.Now().Sub(lastSent)
- if diff < pto {
- t.Fatalf("expected payload was received before the probe timeout, got: %v, want: %v", diff, pto)
- }
- closeSACKConnection(t, dut, conn, acceptFd, listenFd)
-}
-
-// TestRACKWithSACK tests that RACK marks the packets as lost after receiving
-// the ACK for retransmitted packets.
-// See: https://tools.ietf.org/html/draft-ietf-tcpm-rack-08#section-8.1
-func TestRACKWithSACK(t *testing.T) {
- dut, conn, acceptFd, listenFd := createSACKConnection(t)
- seqNum1 := *conn.RemoteSeqNum(t)
-
- // Send ACK for data packets to establish RTT.
- sendAndReceive(t, dut, conn, numPktsForRTT, acceptFd, true /* sendACK */)
- seqNum1.UpdateForward(seqnum.Size(numPktsForRTT * payloadSize))
-
- // We are not sending ACK for these packets.
- const numPkts = 3
- sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */)
-
- time.Sleep(simulatedRTT)
- // SACK for #2 packet.
- sackBlock := make([]byte, 40)
- start := seqNum1.Add(seqnum.Size(payloadSize))
- end := start.Add(seqnum.Size(payloadSize))
- sbOff := 0
- sbOff += header.EncodeNOP(sackBlock[sbOff:])
- sbOff += header.EncodeNOP(sackBlock[sbOff:])
- sbOff += header.EncodeSACKBlocks([]header.SACKBlock{{
- start, end,
- }}, sackBlock[sbOff:])
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]})
-
- rtt, _ := getRTTAndRTO(t, dut, acceptFd)
- timeout := 2 * rtt
- // RACK marks #1 packet as lost after RTT+reorderWindow(RTT/4) and
- // retransmits it.
- if _, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(seqNum1))}, timeout); err != nil {
- t.Fatalf("expected payload was not received: %s", err)
- }
-
- time.Sleep(simulatedRTT)
- // ACK for #1 packet.
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(end))})
-
- // RACK considers transmission times of the packets to mark them lost.
- // As the 3rd packet was sent before the retransmitted 1st packet, RACK
- // marks it as lost and retransmits it..
- expectedSeqNum := testbench.Uint32(uint32(seqNum1) + uint32((numPkts-1)*payloadSize))
- if _, err := conn.Expect(t, testbench.TCP{SeqNum: expectedSeqNum}, timeout); err != nil {
- t.Fatalf("expected payload was not received: %s", err)
- }
- closeSACKConnection(t, dut, conn, acceptFd, listenFd)
-}
-
-// TestRACKWithoutReorder tests that without reordering RACK will retransmit the
-// lost packets after reorder timer expires.
-func TestRACKWithoutReorder(t *testing.T) {
- dut, conn, acceptFd, listenFd := createSACKConnection(t)
- seqNum1 := *conn.RemoteSeqNum(t)
-
- // Send ACK for data packets to establish RTT.
- sendAndReceive(t, dut, conn, numPktsForRTT, acceptFd, true /* sendACK */)
- seqNum1.UpdateForward(seqnum.Size(numPktsForRTT * payloadSize))
-
- // We are not sending ACK for these packets.
- const numPkts = 4
- sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */)
-
- // SACK for [3,4] packets.
- sackBlock := make([]byte, 40)
- start := seqNum1.Add(seqnum.Size(2 * payloadSize))
- end := start.Add(seqnum.Size(2 * payloadSize))
- sbOff := 0
- sbOff += header.EncodeNOP(sackBlock[sbOff:])
- sbOff += header.EncodeNOP(sackBlock[sbOff:])
- sbOff += header.EncodeSACKBlocks([]header.SACKBlock{{
- start, end,
- }}, sackBlock[sbOff:])
- time.Sleep(simulatedRTT)
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]})
-
- // RACK marks #1 and #2 packets as lost and retransmits both after
- // RTT + reorderWindow. The reorderWindow initially will be a small
- // fraction of RTT.
- rtt, _ := getRTTAndRTO(t, dut, acceptFd)
- timeout := 2 * rtt
- for i, sn := 0, seqNum1; i < 2; i++ {
- if _, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(sn))}, timeout); err != nil {
- t.Fatalf("expected payload was not received: %s", err)
- }
- sn.UpdateForward(seqnum.Size(payloadSize))
- }
- closeSACKConnection(t, dut, conn, acceptFd, listenFd)
-}
-
-// TestRACKWithReorder tests that RACK will retransmit segments when there is
-// reordering in the connection and reorder timer expires.
-func TestRACKWithReorder(t *testing.T) {
- dut, conn, acceptFd, listenFd := createSACKConnection(t)
- seqNum1 := *conn.RemoteSeqNum(t)
-
- // Send ACK for data packets to establish RTT.
- sendAndReceive(t, dut, conn, numPktsForRTT, acceptFd, true /* sendACK */)
- seqNum1.UpdateForward(seqnum.Size(numPktsForRTT * payloadSize))
-
- // We are not sending ACK for these packets.
- const numPkts = 4
- sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */)
-
- time.Sleep(simulatedRTT)
- // SACK in reverse order for the connection to detect reorder.
- var start seqnum.Value
- var end seqnum.Value
- for i := 0; i < numPkts-1; i++ {
- sackBlock := make([]byte, 40)
- sbOff := 0
- start = seqNum1.Add(seqnum.Size((numPkts - i - 1) * payloadSize))
- end = start.Add(seqnum.Size((i + 1) * payloadSize))
- sackBlock = make([]byte, 40)
- sbOff = 0
- sbOff += header.EncodeNOP(sackBlock[sbOff:])
- sbOff += header.EncodeNOP(sackBlock[sbOff:])
- sbOff += header.EncodeSACKBlocks([]header.SACKBlock{{
- start, end,
- }}, sackBlock[sbOff:])
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]})
- }
-
- // Send a DSACK block indicating both original and retransmitted
- // packets are received, RACK will increase the reordering window on
- // every DSACK.
- dsackBlock := make([]byte, 40)
- dbOff := 0
- start = seqNum1
- end = start.Add(seqnum.Size(2 * payloadSize))
- dbOff += header.EncodeNOP(dsackBlock[dbOff:])
- dbOff += header.EncodeNOP(dsackBlock[dbOff:])
- dbOff += header.EncodeSACKBlocks([]header.SACKBlock{{
- start, end,
- }}, dsackBlock[dbOff:])
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1 + numPkts*payloadSize)), Options: dsackBlock[:dbOff]})
-
- seqNum1.UpdateForward(seqnum.Size(numPkts * payloadSize))
- sendTime := time.Now()
- sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */)
-
- time.Sleep(simulatedRTT)
- // Send SACK for [2-5] packets.
- sackBlock := make([]byte, 40)
- sbOff := 0
- start = seqNum1.Add(seqnum.Size(payloadSize))
- end = start.Add(seqnum.Size(3 * payloadSize))
- sbOff += header.EncodeNOP(sackBlock[sbOff:])
- sbOff += header.EncodeNOP(sackBlock[sbOff:])
- sbOff += header.EncodeSACKBlocks([]header.SACKBlock{{
- start, end,
- }}, sackBlock[sbOff:])
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]})
-
- // Expect the retransmission of #1 packet after RTT+ReorderWindow.
- if _, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(seqNum1))}, time.Second); err != nil {
- t.Fatalf("expected payload was not received: %s", err)
- }
- rtt, _ := getRTTAndRTO(t, dut, acceptFd)
- diff := time.Now().Sub(sendTime)
- if diff < rtt {
- t.Fatalf("expected payload was received too sonn, within RTT")
- }
-
- closeSACKConnection(t, dut, conn, acceptFd, listenFd)
-}
-
-// TestRACKWithLostRetransmission tests that RACK will not enter RTO when a
-// retransmitted segment is lost and enters fast recovery.
-func TestRACKWithLostRetransmission(t *testing.T) {
- dut, conn, acceptFd, listenFd := createSACKConnection(t)
- seqNum1 := *conn.RemoteSeqNum(t)
-
- // Send ACK for data packets to establish RTT.
- sendAndReceive(t, dut, conn, numPktsForRTT, acceptFd, true /* sendACK */)
- seqNum1.UpdateForward(seqnum.Size(numPktsForRTT * payloadSize))
-
- // We are not sending ACK for these packets.
- const numPkts = 5
- sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */)
-
- // SACK for [2-5] packets.
- sackBlock := make([]byte, 40)
- start := seqNum1.Add(seqnum.Size(payloadSize))
- end := start.Add(seqnum.Size(4 * payloadSize))
- sbOff := 0
- sbOff += header.EncodeNOP(sackBlock[sbOff:])
- sbOff += header.EncodeNOP(sackBlock[sbOff:])
- sbOff += header.EncodeSACKBlocks([]header.SACKBlock{{
- start, end,
- }}, sackBlock[sbOff:])
- time.Sleep(simulatedRTT)
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]})
-
- // RACK marks #1 packet as lost and retransmits it after
- // RTT + reorderWindow. The reorderWindow is bounded between a small
- // fraction of RTT and 1 RTT.
- rtt, _ := getRTTAndRTO(t, dut, acceptFd)
- timeout := 2 * rtt
- if _, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(seqNum1))}, timeout); err != nil {
- t.Fatalf("expected payload was not received: %s", err)
- }
-
- // Send #6 packet.
- payload := make([]byte, payloadSize)
- dut.Send(t, acceptFd, payload, 0)
- gotOne, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(seqNum1 + 5*payloadSize))}, time.Second)
- if err != nil {
- t.Fatalf("Expect #6: %s", err)
- }
- if gotOne == nil {
- t.Fatalf("#6: expected a packet within a second but got none")
- }
-
- // SACK for [2-6] packets.
- sackBlock1 := make([]byte, 40)
- start = seqNum1.Add(seqnum.Size(payloadSize))
- end = start.Add(seqnum.Size(5 * payloadSize))
- sbOff1 := 0
- sbOff1 += header.EncodeNOP(sackBlock1[sbOff1:])
- sbOff1 += header.EncodeNOP(sackBlock1[sbOff1:])
- sbOff1 += header.EncodeSACKBlocks([]header.SACKBlock{{
- start, end,
- }}, sackBlock1[sbOff1:])
- time.Sleep(simulatedRTT)
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock1[:sbOff1]})
-
- // Expect re-retransmission of #1 packet without entering an RTO.
- if _, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(seqNum1))}, timeout); err != nil {
- t.Fatalf("expected payload was not received: %s", err)
- }
-
- // Check the congestion control state.
- info := dut.GetSockOptTCPInfo(t, acceptFd)
- if info.CaState != linux.TCP_CA_Recovery {
- t.Fatalf("expected connection to be in fast recovery, want: %v got: %v", linux.TCP_CA_Recovery, info.CaState)
- }
-
- closeSACKConnection(t, dut, conn, acceptFd, listenFd)
-}
diff --git a/test/packetimpact/tests/tcp_rcv_buf_space_test.go b/test/packetimpact/tests/tcp_rcv_buf_space_test.go
deleted file mode 100644
index f121d44eb..000000000
--- a/test/packetimpact/tests/tcp_rcv_buf_space_test.go
+++ /dev/null
@@ -1,78 +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_rcv_buf_space_test
-
-import (
- "context"
- "flag"
- "testing"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-// TestReduceRecvBuf tests that a packet within window is still dropped
-// if the available buffer space drops below the size of the incoming
-// segment.
-func TestReduceRecvBuf(t *testing.T) {
- dut := testbench.NewDUT(t)
- listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(t, listenFd)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- defer conn.Close(t)
-
- conn.Connect(t)
- acceptFd, _ := dut.Accept(t, listenFd)
- defer dut.Close(t, acceptFd)
-
- // Set a small receive buffer for the test.
- const rcvBufSz = 4096
- dut.SetSockOptInt(t, acceptFd, unix.SOL_SOCKET, unix.SO_RCVBUF, rcvBufSz)
-
- // Retrieve the actual buffer.
- bufSz := dut.GetSockOptInt(t, acceptFd, unix.SOL_SOCKET, unix.SO_RCVBUF)
-
- // Generate a payload of 1 more than the actual buffer size used by the
- // DUT.
- sampleData := testbench.GenerateRandomPayload(t, int(bufSz)+1)
- // Send and receive sample data to the dut.
- const pktSize = 1400
- for payload := sampleData; len(payload) != 0; {
- payloadBytes := pktSize
- if l := len(payload); l < payloadBytes {
- payloadBytes = l
- }
-
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, []testbench.Layer{&testbench.Payload{Bytes: payload[:payloadBytes]}}...)
- payload = payload[payloadBytes:]
- }
-
- // First read should read < len(sampleData)
- if ret, _, err := dut.RecvWithErrno(context.Background(), t, acceptFd, int32(len(sampleData)), 0); ret == -1 || int(ret) == len(sampleData) {
- t.Fatalf("dut.RecvWithErrno(ctx, t, %d, %d, 0) = %d,_, %s", acceptFd, int32(len(sampleData)), ret, err)
- }
-
- // Second read should return EAGAIN as the last segment should have been
- // dropped due to it exceeding the receive buffer space available in the
- // socket.
- if ret, got, err := dut.RecvWithErrno(context.Background(), t, acceptFd, int32(len(sampleData)), unix.MSG_DONTWAIT); got != nil || ret != -1 || err != unix.EAGAIN {
- t.Fatalf("expected no packets but got: %s", got)
- }
-}
diff --git a/test/packetimpact/tests/tcp_retransmits_test.go b/test/packetimpact/tests/tcp_retransmits_test.go
deleted file mode 100644
index d3fb789f4..000000000
--- a/test/packetimpact/tests/tcp_retransmits_test.go
+++ /dev/null
@@ -1,94 +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_retransmits_test
-
-import (
- "bytes"
- "flag"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-func getRTO(t *testing.T, dut testbench.DUT, acceptFd int32) (rto time.Duration) {
- info := dut.GetSockOptTCPInfo(t, acceptFd)
- return time.Duration(info.RTO) * time.Microsecond
-}
-
-// TestRetransmits tests retransmits occur at exponentially increasing
-// time intervals.
-func TestRetransmits(t *testing.T) {
- dut := testbench.NewDUT(t)
- listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(t, listenFd)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- defer conn.Close(t)
-
- conn.Connect(t)
- acceptFd, _ := dut.Accept(t, listenFd)
- defer dut.Close(t, acceptFd)
-
- dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
-
- sampleData := []byte("Sample Data")
- samplePayload := &testbench.Payload{Bytes: sampleData}
-
- // Give a chance for the dut to estimate RTO with RTT from the DATA-ACK.
- // This is to reduce the test run-time from the default initial RTO of 1s.
- // TODO(gvisor.dev/issue/2685) Estimate RTO during handshake, after which
- // we can skip this data send/recv which is solely to estimate RTO.
- dut.Send(t, acceptFd, sampleData, 0)
- if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil {
- t.Fatalf("expected payload was not received: %s", err)
- }
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload)
- if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, nil, time.Second); err != nil {
- t.Fatalf("expected packet was not received: %s", err)
- }
- // Wait for the DUT to receive the data, thus ensuring that the stack has
- // estimated RTO before we query RTO via TCP_INFO.
- if got := dut.Recv(t, acceptFd, int32(len(sampleData)), 0); !bytes.Equal(got, sampleData) {
- t.Fatalf("got dut.Recv(t, %d, %d, 0) = %s, want %s", acceptFd, len(sampleData), got, sampleData)
- }
-
- const timeoutCorrection = time.Second
- const diffCorrection = 200 * time.Millisecond
- rto := getRTO(t, dut, acceptFd)
-
- dut.Send(t, acceptFd, sampleData, 0)
- seq := testbench.Uint32(uint32(*conn.RemoteSeqNum(t)))
- if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: seq}, samplePayload, rto+timeoutCorrection); err != nil {
- t.Fatalf("expected payload was not received: %s", err)
- }
-
- // Expect retransmits of the same segment.
- for i := 0; i < 5; i++ {
- startTime := time.Now()
- rto = getRTO(t, dut, acceptFd)
- if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: seq}, samplePayload, rto+timeoutCorrection); err != nil {
- t.Fatalf("expected payload was not received within %s loop %d err %s", rto+timeoutCorrection, i, err)
- }
- if diff := time.Since(startTime); diff+diffCorrection < rto {
- t.Fatalf("retransmit came sooner got: %s want: >= %s probe %d", diff+diffCorrection, rto, i)
- }
- }
-}
diff --git a/test/packetimpact/tests/tcp_send_window_sizes_piggyback_test.go b/test/packetimpact/tests/tcp_send_window_sizes_piggyback_test.go
deleted file mode 100644
index 64b7288fb..000000000
--- a/test/packetimpact/tests/tcp_send_window_sizes_piggyback_test.go
+++ /dev/null
@@ -1,103 +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_send_window_sizes_piggyback_test
-
-import (
- "flag"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-// TestSendWindowSizesPiggyback tests cases where segment sizes are close to
-// sender window size and checks for ACK piggybacking for each of those case.
-func TestSendWindowSizesPiggyback(t *testing.T) {
- sampleData := []byte("Sample Data")
- segmentSize := uint16(len(sampleData))
- // Advertise receive window sizes that are lesser, equal to or greater than
- // enqueued segment size and check for segment transmits. The test attempts
- // to enqueue a segment on the dut before acknowledging previous segment and
- // lets the dut piggyback any ACKs along with the enqueued segment.
- for _, tt := range []struct {
- description string
- windowSize uint16
- expectedPayload1 []byte
- expectedPayload2 []byte
- enqueue bool
- }{
- // Expect the first segment to be split as it cannot be accomodated in
- // the sender window. This means we need not enqueue a new segment after
- // the first segment.
- {"WindowSmallerThanSegment", segmentSize - 1, sampleData[:(segmentSize - 1)], sampleData[(segmentSize - 1):], false /* enqueue */},
-
- {"WindowEqualToSegment", segmentSize, sampleData, sampleData, true /* enqueue */},
-
- // Expect the second segment to not be split as its size is greater than
- // the available sender window size. The segments should not be split
- // when there is pending unacknowledged data and the segment-size is
- // greater than available sender window.
- {"WindowGreaterThanSegment", segmentSize + 1, sampleData, sampleData, true /* enqueue */},
- } {
- t.Run(tt.description, func(t *testing.T) {
- dut := testbench.NewDUT(t)
- listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(t, listenFd)
-
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort, WindowSize: testbench.Uint16(tt.windowSize)}, testbench.TCP{SrcPort: &remotePort})
- defer conn.Close(t)
-
- conn.Connect(t)
- acceptFd, _ := dut.Accept(t, listenFd)
- defer dut.Close(t, acceptFd)
-
- dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
-
- expectedTCP := testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}
-
- dut.Send(t, acceptFd, sampleData, 0)
- expectedPayload := testbench.Payload{Bytes: tt.expectedPayload1}
- if _, err := conn.ExpectData(t, &expectedTCP, &expectedPayload, time.Second); err != nil {
- t.Fatalf("expected payload was not received: %s", err)
- }
-
- // Expect any enqueued segment to be transmitted by the dut along with
- // piggybacked ACK for our data.
-
- if tt.enqueue {
- // Enqueue a segment for the dut to transmit.
- dut.Send(t, acceptFd, sampleData, 0)
- }
-
- // Send ACK for the previous segment along with data for the dut to
- // receive and ACK back. Sending this ACK would make room for the dut
- // to transmit any enqueued segment.
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh), WindowSize: testbench.Uint16(tt.windowSize)}, &testbench.Payload{Bytes: sampleData})
-
- // Expect the dut to piggyback the ACK for received data along with
- // the segment enqueued for transmit.
- expectedPayload = testbench.Payload{Bytes: tt.expectedPayload2}
- if _, err := conn.ExpectData(t, &expectedTCP, &expectedPayload, time.Second); err != nil {
- t.Fatalf("expected payload was not received: %s", err)
- }
- })
- }
-}
diff --git a/test/packetimpact/tests/tcp_syncookie_test.go b/test/packetimpact/tests/tcp_syncookie_test.go
deleted file mode 100644
index 6be09996b..000000000
--- a/test/packetimpact/tests/tcp_syncookie_test.go
+++ /dev/null
@@ -1,151 +0,0 @@
-// Copyright 2021 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_syncookie_test
-
-import (
- "flag"
- "fmt"
- "math"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-// TestTCPSynCookie tests for ACK handling for connections in SYNRCVD state
-// connections with and without syncookies. It verifies if the passive open
-// connection is indeed using syncookies before proceeding.
-func TestTCPSynCookie(t *testing.T) {
- dut := testbench.NewDUT(t)
- for _, tt := range []struct {
- accept bool
- flags header.TCPFlags
- }{
- {accept: true, flags: header.TCPFlagAck},
- {accept: true, flags: header.TCPFlagAck | header.TCPFlagPsh},
- {accept: false, flags: header.TCPFlagAck | header.TCPFlagSyn},
- {accept: true, flags: header.TCPFlagAck | header.TCPFlagFin},
- {accept: false, flags: header.TCPFlagAck | header.TCPFlagRst},
- {accept: false, flags: header.TCPFlagRst},
- } {
- t.Run(fmt.Sprintf("flags=%s", tt.flags), func(t *testing.T) {
- // Make a copy before parallelizing the test and refer to that
- // within the test. Otherwise, the test reference could be pointing
- // to an incorrect variant based on how it is scheduled.
- test := tt
-
- t.Parallel()
-
- // Listening endpoint accepts one more connection than the listen
- // backlog. Listener starts using syncookies when it sees a new SYN
- // and has backlog size of connections in SYNRCVD state. Keep the
- // listen backlog 1, so that the test can define 2 connections
- // without and with using syncookies.
- listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/)
- defer dut.Close(t, listenFD)
-
- var withoutSynCookieConn testbench.TCPIPv4
- var withSynCookieConn testbench.TCPIPv4
-
- for _, conn := range []*testbench.TCPIPv4{&withoutSynCookieConn, &withSynCookieConn} {
- *conn = dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- }
- defer withoutSynCookieConn.Close(t)
- defer withSynCookieConn.Close(t)
-
- // Setup the 2 connections in SYNRCVD state and verify if one of the
- // connection is indeed using syncookies by checking for absence of
- // SYNACK retransmits.
- for _, c := range []struct {
- desc string
- conn *testbench.TCPIPv4
- expectRetransmit bool
- }{
- {desc: "without syncookies", conn: &withoutSynCookieConn, expectRetransmit: true},
- {desc: "with syncookies", conn: &withSynCookieConn, expectRetransmit: false},
- } {
- t.Run(c.desc, func(t *testing.T) {
- // Expect dut connection to have transitioned to SYNRCVD state.
- c.conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)})
- if _, err := c.conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err != nil {
- t.Fatalf("expected SYNACK, but got %s", err)
- }
-
- // If the DUT listener is using syn cookies, it will not retransmit SYNACK.
- got, err := c.conn.ExpectData(t, &testbench.TCP{SeqNum: testbench.Uint32(uint32(*c.conn.RemoteSeqNum(t) - 1)), Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, nil, 2*time.Second)
- if c.expectRetransmit && err != nil {
- t.Fatalf("expected retransmitted SYNACK, but got %s", err)
- }
- if !c.expectRetransmit && err == nil {
- t.Fatalf("expected no retransmitted SYNACK, but got %s", got)
- }
- })
- }
-
- // Check whether ACKs with the given flags completes the handshake.
- for _, c := range []struct {
- desc string
- conn *testbench.TCPIPv4
- }{
- {desc: "with syncookies", conn: &withSynCookieConn},
- {desc: "without syncookies", conn: &withoutSynCookieConn},
- } {
- t.Run(c.desc, func(t *testing.T) {
- pfds := dut.Poll(t, []unix.PollFd{{Fd: listenFD, Events: math.MaxInt16}}, 0 /*timeout*/)
- if got, want := len(pfds), 0; got != want {
- t.Fatalf("dut.Poll(...) = %d, want = %d", got, want)
- }
-
- sampleData := []byte("Sample Data")
- samplePayload := &testbench.Payload{Bytes: sampleData}
-
- c.conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(test.flags)}, samplePayload)
- pfds = dut.Poll(t, []unix.PollFd{{Fd: listenFD, Events: unix.POLLIN}}, time.Second)
- want := 0
- if test.accept {
- want = 1
- }
- if got := len(pfds); got != want {
- t.Fatalf("got dut.Poll(...) = %d, want = %d", got, want)
- }
- // Accept the connection to enable poll on any subsequent connection.
- if test.accept {
- fd, _ := dut.Accept(t, listenFD)
- if test.flags.Contains(header.TCPFlagFin) {
- if dut.Uname.IsLinux() {
- dut.PollOne(t, fd, unix.POLLIN|unix.POLLRDHUP, time.Second)
- } else {
- // TODO(gvisor.dev/issue/6015): Notify POLLIN|POLLRDHUP on incoming FIN.
- dut.PollOne(t, fd, unix.POLLIN, time.Second)
- }
- }
- got := dut.Recv(t, fd, int32(len(sampleData)), 0)
- if diff := cmp.Diff(got, sampleData); diff != "" {
- t.Fatalf("dut.Recv: data mismatch (-want +got):\n%s", diff)
- }
- dut.Close(t, fd)
- }
- })
- }
- })
- }
-}
diff --git a/test/packetimpact/tests/tcp_synrcvd_reset_test.go b/test/packetimpact/tests/tcp_synrcvd_reset_test.go
deleted file mode 100644
index 3346d43c4..000000000
--- a/test/packetimpact/tests/tcp_synrcvd_reset_test.go
+++ /dev/null
@@ -1,60 +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_syn_reset_test
-
-import (
- "flag"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-// TestTCPSynRcvdReset tests transition from SYN-RCVD to CLOSED.
-func TestTCPSynRcvdReset(t *testing.T) {
- dut := testbench.NewDUT(t)
- listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(t, listenFD)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- defer conn.Close(t)
-
- // Expect dut connection to have transitioned to SYN-RCVD state.
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)})
- if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err != nil {
- t.Fatalf("expected SYN-ACK %s", err)
- }
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)})
-
- // Expect the connection to have transitioned SYN-RCVD to CLOSED.
- //
- // Retransmit the ACK a few times to give time for the DUT to transition to
- // CLOSED. We cannot use TCP_INFO to lookup the state as this is a passive
- // DUT connection.
- for i := 0; i < 5; i++ {
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
- if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, nil, time.Second); err != nil {
- t.Logf("retransmit%d ACK as we did not get the expected RST, %s", i, err)
- continue
- }
- return
- }
- t.Fatal("did not receive a TCP RST")
-}
diff --git a/test/packetimpact/tests/tcp_synsent_reset_test.go b/test/packetimpact/tests/tcp_synsent_reset_test.go
deleted file mode 100644
index fe53e7061..000000000
--- a/test/packetimpact/tests/tcp_synsent_reset_test.go
+++ /dev/null
@@ -1,94 +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_synsent_reset_test
-
-import (
- "flag"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-// dutSynSentState sets up the dut connection in SYN-SENT state.
-func dutSynSentState(t *testing.T) (*testbench.DUT, *testbench.TCPIPv4, int32, uint16, uint16) {
- t.Helper()
-
- dut := testbench.NewDUT(t)
-
- clientFD, clientPort := dut.CreateBoundSocket(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, dut.Net.RemoteIPv4)
- port := uint16(9001)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{SrcPort: &port, DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort, DstPort: &port})
-
- sa := unix.SockaddrInet4{Port: int(port)}
- copy(sa.Addr[:], dut.Net.LocalIPv4)
- // Bring the dut to SYN-SENT state with a non-blocking connect.
- dut.Connect(t, clientFD, &sa)
- if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, nil, time.Second); err != nil {
- t.Fatalf("expected SYN\n")
- }
-
- return &dut, &conn, clientFD, port, clientPort
-}
-
-// TestTCPSynSentReset tests RFC793, p67: SYN-SENT to CLOSED transition.
-func TestTCPSynSentReset(t *testing.T) {
- dut, conn, fd, _, _ := dutSynSentState(t)
- defer conn.Close(t)
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst | header.TCPFlagAck)})
- // Expect the connection to have closed.
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
- if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, nil, time.Second); err != nil {
- t.Fatalf("expected a TCP RST")
- }
- info := dut.GetSockOptTCPInfo(t, fd)
- if got, want := uint32(info.State), linux.TCP_CLOSE; got != want {
- t.Fatalf("got %d want %d", got, want)
- }
-}
-
-// TestTCPSynSentRcvdReset tests RFC793, p70, SYN-SENT to SYN-RCVD to CLOSED
-// transitions.
-func TestTCPSynSentRcvdReset(t *testing.T) {
- dut, c, fd, remotePort, clientPort := dutSynSentState(t)
- defer c.Close(t)
-
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{SrcPort: &remotePort, DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort, DstPort: &remotePort})
- defer conn.Close(t)
- // Initiate new SYN connection with the same port pair
- // (simultaneous open case), expect the dut connection to move to
- // SYN-RCVD state
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)})
- if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err != nil {
- t.Fatalf("expected SYN-ACK %s\n", err)
- }
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)})
- // Expect the connection to have transitioned SYN-RCVD to CLOSED.
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
- if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, nil, time.Second); err != nil {
- t.Fatalf("expected a TCP RST")
- }
- info := dut.GetSockOptTCPInfo(t, fd)
- if got, want := uint32(info.State), linux.TCP_CLOSE; got != want {
- t.Fatalf("got %d want %d", got, want)
- }
-}
diff --git a/test/packetimpact/tests/tcp_timewait_reset_test.go b/test/packetimpact/tests/tcp_timewait_reset_test.go
deleted file mode 100644
index 89037f0a4..000000000
--- a/test/packetimpact/tests/tcp_timewait_reset_test.go
+++ /dev/null
@@ -1,67 +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_timewait_reset_test
-
-import (
- "flag"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-// TestTimeWaitReset tests handling of RST when in TIME_WAIT state.
-func TestTimeWaitReset(t *testing.T) {
- dut := testbench.NewDUT(t)
- listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/)
- defer dut.Close(t, listenFD)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- defer conn.Close(t)
-
- conn.Connect(t)
- acceptFD, _ := dut.Accept(t, listenFD)
-
- // Trigger active close.
- dut.Close(t, acceptFD)
-
- _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second)
- if err != nil {
- t.Fatalf("expected a FIN: %s", err)
- }
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
- // Send a FIN, DUT should transition to TIME_WAIT from FIN_WAIT2.
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)})
- if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil {
- t.Fatalf("expected an ACK for our FIN: %s", err)
- }
-
- // Send a RST, the DUT should transition to CLOSED from TIME_WAIT.
- // This is the default Linux behavior, it can be changed to ignore RSTs via
- // sysctl net.ipv4.tcp_rfc1337.
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)})
-
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
- // The DUT should reply with RST to our ACK as the state should have
- // transitioned to CLOSED.
- if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, time.Second); err != nil {
- t.Fatalf("expected a RST: %s", err)
- }
-}
diff --git a/test/packetimpact/tests/tcp_unacc_seq_ack_test.go b/test/packetimpact/tests/tcp_unacc_seq_ack_test.go
deleted file mode 100644
index 389bfc629..000000000
--- a/test/packetimpact/tests/tcp_unacc_seq_ack_test.go
+++ /dev/null
@@ -1,274 +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_unacc_seq_ack_test
-
-import (
- "flag"
- "fmt"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/seqnum"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-func TestEstablishedUnaccSeqAck(t *testing.T) {
- for _, tt := range []struct {
- description string
- makeTestingTCP func(t *testing.T, conn *testbench.TCPIPv4, seqNumOffset, windowSize seqnum.Size) testbench.TCP
- seqNumOffset seqnum.Size
- expectAck bool
- restoreSeq bool
- }{
- {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 0, expectAck: true, restoreSeq: true},
- {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 1, expectAck: true, restoreSeq: true},
- {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 2, expectAck: true, restoreSeq: true},
- {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 0, expectAck: true, restoreSeq: false},
- {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 1, expectAck: false, restoreSeq: true},
- {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 2, expectAck: false, restoreSeq: true},
- } {
- t.Run(fmt.Sprintf("%s:offset=%d", tt.description, tt.seqNumOffset), func(t *testing.T) {
- dut := testbench.NewDUT(t)
- listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/)
- defer dut.Close(t, listenFD)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- defer conn.Close(t)
-
- conn.Connect(t)
- dut.Accept(t, listenFD)
-
- sampleData := []byte("Sample Data")
- samplePayload := &testbench.Payload{Bytes: sampleData}
-
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload)
- gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second)
- if err != nil {
- t.Fatalf("expected ack %s", err)
- }
- windowSize := seqnum.Size(*gotTCP.WindowSize)
-
- origSeq := *conn.LocalSeqNum(t)
- // Send a segment with OTW Seq / unacc ACK.
- conn.Send(t, tt.makeTestingTCP(t, &conn, tt.seqNumOffset, windowSize), samplePayload)
- if tt.restoreSeq {
- // Restore the local sequence number to ensure that the incoming
- // ACK matches the TCP layer state.
- *conn.LocalSeqNum(t) = origSeq
- }
- gotAck, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second)
- if tt.expectAck && err != nil {
- t.Fatalf("expected an ack but got none: %s", err)
- }
- if err == nil && !tt.expectAck && gotAck != nil {
- t.Fatalf("expected no ack but got one: %s", gotAck)
- }
- })
- }
-}
-
-func TestPassiveCloseUnaccSeqAck(t *testing.T) {
- for _, tt := range []struct {
- description string
- makeTestingTCP func(t *testing.T, conn *testbench.TCPIPv4, seqNumOffset, windowSize seqnum.Size) testbench.TCP
- seqNumOffset seqnum.Size
- expectAck bool
- }{
- {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 0, expectAck: false},
- {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 1, expectAck: true},
- {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 2, expectAck: true},
- {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 0, expectAck: false},
- {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 1, expectAck: true},
- {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 2, expectAck: true},
- } {
- t.Run(fmt.Sprintf("%s:offset=%d", tt.description, tt.seqNumOffset), func(t *testing.T) {
- dut := testbench.NewDUT(t)
- listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/)
- defer dut.Close(t, listenFD)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- defer conn.Close(t)
-
- conn.Connect(t)
- acceptFD, _ := dut.Accept(t, listenFD)
-
- // Send a FIN to DUT to intiate the passive close.
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagFin)})
- gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second)
- if err != nil {
- t.Fatalf("expected an ACK for our fin and DUT should enter CLOSE_WAIT: %s", err)
- }
- windowSize := seqnum.Size(*gotTCP.WindowSize)
-
- sampleData := []byte("Sample Data")
- samplePayload := &testbench.Payload{Bytes: sampleData}
-
- // Send a segment with OTW Seq / unacc ACK.
- conn.Send(t, tt.makeTestingTCP(t, &conn, tt.seqNumOffset, windowSize), samplePayload)
- gotAck, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second)
- if tt.expectAck && err != nil {
- t.Errorf("expected an ack but got none: %s", err)
- }
- if err == nil && !tt.expectAck && gotAck != nil {
- t.Errorf("expected no ack but got one: %s", gotAck)
- }
-
- // Now let's verify DUT is indeed in CLOSE_WAIT
- dut.Close(t, acceptFD)
- if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(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(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
- // Send some extra data to DUT
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, samplePayload)
- if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, time.Second); err != nil {
- t.Fatalf("expected DUT to send an RST: %s", err)
- }
- })
- }
-}
-
-func TestActiveCloseUnaccpSeqAck(t *testing.T) {
- for _, tt := range []struct {
- description string
- makeTestingTCP func(t *testing.T, conn *testbench.TCPIPv4, seqNumOffset, windowSize seqnum.Size) testbench.TCP
- seqNumOffset seqnum.Size
- restoreSeq bool
- }{
- {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 0, restoreSeq: true},
- {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 1, restoreSeq: true},
- {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 2, restoreSeq: true},
- {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 0, restoreSeq: false},
- {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 1, restoreSeq: true},
- {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 2, restoreSeq: true},
- } {
- t.Run(fmt.Sprintf("%s:offset=%d", tt.description, tt.seqNumOffset), func(t *testing.T) {
- dut := testbench.NewDUT(t)
- listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/)
- defer dut.Close(t, listenFD)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- defer conn.Close(t)
-
- conn.Connect(t)
- acceptFD, _ := dut.Accept(t, listenFD)
-
- // Trigger active close.
- dut.Shutdown(t, acceptFD, unix.SHUT_WR)
-
- // Get to FIN_WAIT2
- gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second)
- if err != nil {
- t.Fatalf("expected a FIN: %s", err)
- }
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
-
- sendUnaccSeqAck := func(state string) {
- t.Helper()
- sampleData := []byte("Sample Data")
- samplePayload := &testbench.Payload{Bytes: sampleData}
-
- origSeq := *conn.LocalSeqNum(t)
- // Send a segment with OTW Seq / unacc ACK.
- conn.Send(t, tt.makeTestingTCP(t, &conn, tt.seqNumOffset, seqnum.Size(*gotTCP.WindowSize)), samplePayload)
- if tt.restoreSeq {
- // Restore the local sequence number to ensure that the
- // incoming ACK matches the TCP layer state.
- *conn.LocalSeqNum(t) = origSeq
- }
- if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil {
- t.Errorf("expected an ack in %s state, but got none: %s", state, err)
- }
- }
-
- sendUnaccSeqAck("FIN_WAIT2")
-
- // Send a FIN to DUT to get to TIME_WAIT
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)})
- if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil {
- t.Fatalf("expected an ACK for our fin and DUT should enter TIME_WAIT: %s", err)
- }
-
- sendUnaccSeqAck("TIME_WAIT")
- })
- }
-}
-
-func TestSimultaneousCloseUnaccSeqAck(t *testing.T) {
- for _, tt := range []struct {
- description string
- makeTestingTCP func(t *testing.T, conn *testbench.TCPIPv4, seqNumOffset, windowSize seqnum.Size) testbench.TCP
- seqNumOffset seqnum.Size
- expectAck bool
- }{
- {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 0, expectAck: false},
- {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 1, expectAck: true},
- {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 2, expectAck: true},
- {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 0, expectAck: false},
- {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 1, expectAck: true},
- {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 2, expectAck: true},
- } {
- t.Run(fmt.Sprintf("%s:offset=%d", tt.description, tt.seqNumOffset), func(t *testing.T) {
- dut := testbench.NewDUT(t)
- listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/)
- defer dut.Close(t, listenFD)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- defer conn.Close(t)
-
- conn.Connect(t)
- acceptFD, _ := dut.Accept(t, listenFD)
-
- // Trigger active close.
- dut.Shutdown(t, acceptFD, unix.SHUT_WR)
-
- if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil {
- t.Fatalf("expected a FIN: %s", err)
- }
- // Do not ack the FIN from DUT so that we get to CLOSING.
- seqNumForTheirFIN := testbench.Uint32(uint32(*conn.RemoteSeqNum(t)) - 1)
- conn.Send(t, testbench.TCP{AckNum: seqNumForTheirFIN, Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)})
-
- gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second)
- if err != nil {
- t.Errorf("expected an ACK to our FIN, but got none: %s", err)
- }
-
- sampleData := []byte("Sample Data")
- samplePayload := &testbench.Payload{Bytes: sampleData}
-
- origSeq := uint32(*conn.LocalSeqNum(t))
- // Send a segment with OTW Seq / unacc ACK.
- tcp := tt.makeTestingTCP(t, &conn, tt.seqNumOffset, seqnum.Size(*gotTCP.WindowSize))
- if tt.description == "OTWSeq" {
- // If we generate an OTW Seq segment, make sure we don't acknowledge their FIN so that
- // we stay in CLOSING.
- tcp.AckNum = seqNumForTheirFIN
- }
- conn.Send(t, tcp, samplePayload)
-
- got, err := conn.Expect(t, testbench.TCP{AckNum: testbench.Uint32(origSeq), Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second)
- if tt.expectAck && err != nil {
- t.Errorf("expected an ack in CLOSING state, but got none: %s", err)
- }
- if !tt.expectAck && got != nil {
- t.Errorf("expected no ack in CLOSING state, but got one: %s", got)
- }
- })
- }
-}
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 ef38bd738..000000000
--- a/test/packetimpact/tests/tcp_user_timeout_test.go
+++ /dev/null
@@ -1,99 +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 (
- "flag"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-func sendPayload(t *testing.T, conn *testbench.TCPIPv4, dut *testbench.DUT, fd int32) {
- sampleData := make([]byte, 100)
- for i := range sampleData {
- sampleData[i] = uint8(i)
- }
- conn.Drain(t)
- dut.Send(t, fd, sampleData, 0)
- if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, &testbench.Payload{Bytes: sampleData}, time.Second); err != nil {
- t.Fatalf("expected data but got none: %w", err)
- }
-}
-
-func sendFIN(t *testing.T, conn *testbench.TCPIPv4, dut *testbench.DUT, fd int32) {
- dut.Close(t, fd)
-}
-
-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(_ *testing.T, _ *testbench.TCPIPv4, _ *testbench.DUT, fd int32)
- }{
- {"AfterPayload", sendPayload},
- {"AfterFIN", sendFIN},
- } {
- t.Run(tt.description+ttf.description, func(t *testing.T) {
- // Create a socket, listen, TCP handshake, and accept.
- dut := testbench.NewDUT(t)
- listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(t, listenFD)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- defer conn.Close(t)
- conn.Connect(t)
- acceptFD, _ := dut.Accept(t, listenFD)
-
- if tt.userTimeout != 0 {
- dut.SetSockOptInt(t, acceptFD, unix.SOL_TCP, unix.TCP_USER_TIMEOUT, int32(tt.userTimeout.Milliseconds()))
- }
-
- ttf.f(t, &conn, &dut, acceptFD)
-
- time.Sleep(tt.sendDelay)
- conn.Drain(t)
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(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(t, testbench.TCP{Flags: testbench.TCPFlags(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 0d65a2ea2..000000000
--- a/test/packetimpact/tests/tcp_window_shrink_test.go
+++ /dev/null
@@ -1,72 +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 (
- "flag"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-func TestWindowShrink(t *testing.T) {
- dut := testbench.NewDUT(t)
- listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(t, listenFd)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- defer conn.Close(t)
-
- conn.Connect(t)
- acceptFd, _ := dut.Accept(t, listenFd)
- defer dut.Close(t, acceptFd)
-
- dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
-
- sampleData := []byte("Sample Data")
- samplePayload := &testbench.Payload{Bytes: sampleData}
-
- dut.Send(t, acceptFd, sampleData, 0)
- if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil {
- t.Fatalf("expected payload was not received: %s", err)
- }
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
-
- dut.Send(t, acceptFd, sampleData, 0)
- dut.Send(t, acceptFd, sampleData, 0)
- if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil {
- t.Fatalf("expected payload was not received: %s", err)
- }
- if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil {
- t.Fatalf("expected payload was not received: %s", err)
- }
- // We close our receiving window here
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), WindowSize: testbench.Uint16(0)})
-
- dut.Send(t, 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(t) - 1
- if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: testbench.Uint32(uint32(expectedRemoteSeqNum))}, nil, time.Second); err != nil {
- t.Fatalf("expected a packet with sequence number %d: %s", expectedRemoteSeqNum, err)
- }
-}
diff --git a/test/packetimpact/tests/tcp_zero_receive_window_test.go b/test/packetimpact/tests/tcp_zero_receive_window_test.go
deleted file mode 100644
index bd33a2a03..000000000
--- a/test/packetimpact/tests/tcp_zero_receive_window_test.go
+++ /dev/null
@@ -1,202 +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_zero_receive_window_test
-
-import (
- "flag"
- "fmt"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-// TestZeroReceiveWindow tests if the DUT sends a zero receive window eventually.
-func TestZeroReceiveWindow(t *testing.T) {
- for _, payloadLen := range []int{64, 512, 1024} {
- t.Run(fmt.Sprintf("TestZeroReceiveWindow_with_%dbytes_payload", payloadLen), func(t *testing.T) {
- dut := testbench.NewDUT(t)
- listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(t, listenFd)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- defer conn.Close(t)
-
- conn.Connect(t)
- acceptFd, _ := dut.Accept(t, listenFd)
- defer dut.Close(t, acceptFd)
-
- dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
-
- fillRecvBuffer(t, &conn, &dut, acceptFd, payloadLen)
- })
- }
-}
-
-func fillRecvBuffer(t *testing.T, conn *testbench.TCPIPv4, dut *testbench.DUT, acceptFd int32, payloadLen int) {
- // Expect the DUT to eventually advertise zero receive window.
- // The test would timeout otherwise.
- for readOnce := false; ; {
- samplePayload := &testbench.Payload{Bytes: testbench.GenerateRandomPayload(t, payloadLen)}
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload)
- gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second)
- if err != nil {
- t.Fatalf("expected packet was not received: %s", err)
- }
- // Read once to trigger the subsequent window update from the
- // DUT to grow the right edge of the receive window from what
- // was advertised in the SYN-ACK. This ensures that we test
- // for the full default buffer size (1MB on gVisor at the time
- // of writing this comment), thus testing for cases when the
- // scaled receive window size ends up > 65535 (0xffff).
- if !readOnce {
- if got := dut.Recv(t, acceptFd, int32(payloadLen), 0); len(got) != payloadLen {
- t.Fatalf("got dut.Recv(t, %d, %d, 0) = %d, want %d", acceptFd, payloadLen, len(got), payloadLen)
- }
- readOnce = true
- }
- windowSize := *gotTCP.WindowSize
- t.Logf("got window size = %d", windowSize)
- if windowSize == 0 {
- break
- }
- if payloadLen > int(windowSize) {
- payloadLen = int(windowSize)
- }
- }
-}
-
-func TestZeroToNonZeroWindowUpdate(t *testing.T) {
- dut := testbench.NewDUT(t)
- listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(t, listenFd)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- defer conn.Close(t)
-
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)})
- synAck, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, time.Second)
- if err != nil {
- t.Fatalf("didn't get synack during handshake: %s", err)
- }
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
-
- acceptFd, _ := dut.Accept(t, listenFd)
- defer dut.Close(t, acceptFd)
-
- dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
-
- mss := header.ParseSynOptions(synAck.Options, true).MSS
- fillRecvBuffer(t, &conn, &dut, acceptFd, int(mss))
-
- // Read < mss worth of data from the receive buffer and expect the DUT to
- // not send a non-zero window update.
- payloadLen := mss - 1
- if got := dut.Recv(t, acceptFd, int32(payloadLen), 0); len(got) != int(payloadLen) {
- t.Fatalf("got dut.Recv(t, %d, %d, 0) = %d, want %d", acceptFd, payloadLen, len(got), payloadLen)
- }
- // Send a zero-window-probe to force an ACK from the receiver with any
- // window updates.
- conn.Send(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(*conn.LocalSeqNum(t) - 1)), Flags: testbench.TCPFlags(header.TCPFlagAck)})
- gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second)
- if err != nil {
- t.Fatalf("expected packet was not received: %s", err)
- }
- if windowSize := *gotTCP.WindowSize; windowSize != 0 {
- t.Fatalf("got non zero window = %d", windowSize)
- }
-
- // Now, ensure that the DUT eventually sends non-zero window update.
- seqNum := testbench.Uint32(uint32(*conn.LocalSeqNum(t) - 1))
- ackNum := testbench.Uint32(uint32(*conn.LocalSeqNum(t)))
- recvCheckWindowUpdate := func(readLen int) uint16 {
- if got := dut.Recv(t, acceptFd, int32(readLen), 0); len(got) != readLen {
- t.Fatalf("got dut.Recv(t, %d, %d, 0) = %d, want %d", acceptFd, readLen, len(got), readLen)
- }
- conn.Send(t, testbench.TCP{SeqNum: seqNum, Flags: testbench.TCPFlags(header.TCPFlagPsh | header.TCPFlagAck)}, &testbench.Payload{Bytes: make([]byte, 1)})
- gotTCP, err := conn.Expect(t, testbench.TCP{AckNum: ackNum, Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second)
- if err != nil {
- t.Fatalf("expected packet was not received: %s", err)
- }
- return *gotTCP.WindowSize
- }
-
- if !dut.Uname.IsLinux() {
- if win := recvCheckWindowUpdate(1); win == 0 {
- t.Fatal("expected non-zero window update")
- }
- } else {
- // Linux stack takes additional socket reads to send out window update,
- // its a function of sysctl_tcp_rmem among other things.
- // https://github.com/torvalds/linux/blob/7acac4b3196/net/ipv4/tcp_input.c#L687
- for {
- if win := recvCheckWindowUpdate(int(payloadLen)); win != 0 {
- break
- }
- }
- }
-}
-
-// TestNonZeroReceiveWindow tests for the DUT to never send a zero receive
-// window when the data is being read from the socket buffer.
-func TestNonZeroReceiveWindow(t *testing.T) {
- for _, payloadLen := range []int{64, 512, 1024} {
- t.Run(fmt.Sprintf("TestZeroReceiveWindow_with_%dbytes_payload", payloadLen), func(t *testing.T) {
- dut := testbench.NewDUT(t)
- listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(t, listenFd)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- defer conn.Close(t)
-
- conn.Connect(t)
- acceptFd, _ := dut.Accept(t, listenFd)
- defer dut.Close(t, acceptFd)
-
- dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
-
- samplePayload := &testbench.Payload{Bytes: testbench.GenerateRandomPayload(t, payloadLen)}
- var rcvWindow uint16
- initRcv := false
- // This loop keeps a running rcvWindow value from the initial ACK for the data
- // we sent. Once we have received ACKs with non-zero receive windows, we break
- // the loop.
- for {
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload)
- gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second)
- if err != nil {
- t.Fatalf("expected packet was not received: %s", err)
- }
- if got := dut.Recv(t, acceptFd, int32(payloadLen), 0); len(got) != payloadLen {
- t.Fatalf("got dut.Recv(t, %d, %d, 0) = %d, want %d", acceptFd, payloadLen, len(got), payloadLen)
- }
- if *gotTCP.WindowSize == 0 {
- t.Fatalf("expected non-zero receive window.")
- }
- if !initRcv {
- rcvWindow = uint16(*gotTCP.WindowSize)
- initRcv = true
- }
- if rcvWindow <= uint16(payloadLen) {
- break
- }
- rcvWindow -= uint16(payloadLen)
- }
- })
- }
-}
diff --git a/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go b/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go
deleted file mode 100644
index 22b17a39e..000000000
--- a/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go
+++ /dev/null
@@ -1,115 +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_zero_window_probe_retransmit_test
-
-import (
- "bytes"
- "flag"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-// TestZeroWindowProbeRetransmit tests retransmits of zero window probes
-// to be sent at exponentially inreasing time intervals.
-func TestZeroWindowProbeRetransmit(t *testing.T) {
- dut := testbench.NewDUT(t)
- listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(t, listenFd)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- defer conn.Close(t)
-
- conn.Connect(t)
- acceptFd, _ := dut.Accept(t, listenFd)
- defer dut.Close(t, acceptFd)
-
- dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
-
- sampleData := []byte("Sample Data")
- samplePayload := &testbench.Payload{Bytes: sampleData}
-
- // Send and receive sample data to the dut.
- dut.Send(t, acceptFd, sampleData, 0)
- if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil {
- t.Fatalf("expected payload was not received: %s", err)
- }
-
- // Check for the dut to keep the connection alive as long as the zero window
- // probes are acknowledged. Check if the zero window probes are sent at
- // exponentially increasing intervals. The timeout intervals are function
- // of the recorded first zero probe transmission duration.
- //
- // Advertize zero receive window along with a payload.
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh), WindowSize: testbench.Uint16(0)}, samplePayload)
- if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, nil, time.Second); err != nil {
- t.Fatalf("expected packet was not received: %s", err)
- }
- // Wait for the payload to be received by the DUT, which is also an
- // indication of receive of the peer window advertisement.
- if got := dut.Recv(t, acceptFd, int32(len(sampleData)), 0); !bytes.Equal(got, sampleData) {
- t.Fatalf("got dut.Recv(t, %d, %d, 0) = %s, want %s", acceptFd, len(sampleData), got, sampleData)
- }
-
- probeSeq := testbench.Uint32(uint32(*conn.RemoteSeqNum(t) - 1))
- ackProbe := testbench.Uint32(uint32(*conn.RemoteSeqNum(t)))
-
- // Ask the dut to send out data.
- dut.Send(t, acceptFd, sampleData, 0)
-
- var prev time.Duration
- // Expect the dut to keep the connection alive as long as the remote is
- // acknowledging the zero-window probes.
- for i := 1; i <= 5; i++ {
- start := time.Now()
- // Expect zero-window probe with a timeout which is a function of the typical
- // first retransmission time. The retransmission times is supposed to
- // exponentially increase.
- if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: probeSeq}, nil, time.Duration(i)*time.Second); err != nil {
- t.Fatalf("%d: expected a probe with sequence number %d: %s", i, probeSeq, err)
- }
- if i == 1 {
- // Skip the first probe as computing transmit time for that is
- // non-deterministic because of the arbitrary time taken for
- // the dut to receive a send command and issue a send.
- continue
- }
-
- // Check if the time taken to receive the probe from the dut is
- // increasing exponentially. To avoid flakes, use a correction
- // factor for the expected duration which accounts for any
- // scheduling non-determinism.
- const timeCorrection = 200 * time.Millisecond
- got := time.Since(start)
- if want := (2 * prev) - timeCorrection; prev != 0 && got < want {
- t.Errorf("got zero probe %d after %s, want >= %s", i, got, want)
- }
- prev = got
- // Acknowledge the zero-window probes from the dut.
- conn.Send(t, testbench.TCP{AckNum: ackProbe, Flags: testbench.TCPFlags(header.TCPFlagAck), WindowSize: testbench.Uint16(0)})
- }
- // Advertize non-zero window.
- conn.Send(t, testbench.TCP{AckNum: ackProbe, Flags: testbench.TCPFlags(header.TCPFlagAck)})
- // Expect the dut to recover and transmit data.
- if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: ackProbe}, samplePayload, time.Second); err != nil {
- t.Fatalf("expected payload was not received: %s", err)
- }
-}
diff --git a/test/packetimpact/tests/tcp_zero_window_probe_test.go b/test/packetimpact/tests/tcp_zero_window_probe_test.go
deleted file mode 100644
index 8b90fcbe9..000000000
--- a/test/packetimpact/tests/tcp_zero_window_probe_test.go
+++ /dev/null
@@ -1,111 +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_zero_window_probe_test
-
-import (
- "flag"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-// TestZeroWindowProbe tests few cases of zero window probing over the
-// same connection.
-func TestZeroWindowProbe(t *testing.T) {
- dut := testbench.NewDUT(t)
- listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(t, listenFd)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- defer conn.Close(t)
-
- conn.Connect(t)
- acceptFd, _ := dut.Accept(t, listenFd)
- defer dut.Close(t, acceptFd)
-
- dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
-
- sampleData := []byte("Sample Data")
- samplePayload := &testbench.Payload{Bytes: sampleData}
-
- start := time.Now()
- // Send and receive sample data to the dut.
- dut.Send(t, acceptFd, sampleData, 0)
- if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil {
- t.Fatalf("expected payload was not received: %s", err)
- }
- sendTime := time.Now().Sub(start)
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload)
- if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, nil, time.Second); err != nil {
- t.Fatalf("expected packet was not received: %s", err)
- }
-
- // Test 1: Check for receive of a zero window probe, record the duration for
- // probe to be sent.
- //
- // Advertize zero window to the dut.
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), WindowSize: testbench.Uint16(0)})
-
- // Expected sequence number of the zero window probe.
- probeSeq := testbench.Uint32(uint32(*conn.RemoteSeqNum(t) - 1))
- // Expected ack number of the ACK for the probe.
- ackProbe := testbench.Uint32(uint32(*conn.RemoteSeqNum(t)))
-
- // Expect there are no zero-window probes sent until there is data to be sent out
- // from the dut.
- if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: probeSeq}, nil, 2*time.Second); err == nil {
- t.Fatalf("unexpected packet with sequence number %d: %s", probeSeq, err)
- }
-
- start = time.Now()
- // Ask the dut to send out data.
- dut.Send(t, acceptFd, sampleData, 0)
- // Expect zero-window probe from the dut.
- if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: probeSeq}, nil, time.Second); err != nil {
- t.Fatalf("expected a packet with sequence number %d: %s", probeSeq, err)
- }
- // Expect the probe to be sent after some time. Compare against the previous
- // time recorded when the dut immediately sends out data on receiving the
- // send command.
- if startProbeDuration := time.Now().Sub(start); startProbeDuration <= sendTime {
- t.Fatalf("expected the first probe to be sent out after retransmission interval, got %s want > %s", startProbeDuration, sendTime)
- }
-
- // Test 2: Check if the dut recovers on advertizing non-zero receive window.
- // and sends out the sample payload after the send window opens.
- //
- // Advertize non-zero window to the dut and ack the zero window probe.
- conn.Send(t, testbench.TCP{AckNum: ackProbe, Flags: testbench.TCPFlags(header.TCPFlagAck)})
- // Expect the dut to recover and transmit data.
- if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: ackProbe}, samplePayload, time.Second); err != nil {
- t.Fatalf("expected payload was not received: %s", err)
- }
-
- // Test 3: Sanity check for dut's processing of a similar probe it sent.
- // Check if the dut responds as we do for a similar probe sent to it.
- // Basically with sequence number to one byte behind the unacknowledged
- // sequence number.
- p := testbench.Uint32(uint32(*conn.LocalSeqNum(t)))
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), SeqNum: testbench.Uint32(uint32(*conn.LocalSeqNum(t) - 1))})
- if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: p}, nil, time.Second); err != nil {
- t.Fatalf("expected a packet with ack number: %d: %s", p, err)
- }
-}
diff --git a/test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go b/test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go
deleted file mode 100644
index 1ce4d22b7..000000000
--- a/test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go
+++ /dev/null
@@ -1,97 +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_zero_window_probe_usertimeout_test
-
-import (
- "flag"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-// TestZeroWindowProbeUserTimeout sanity tests user timeout when we are
-// retransmitting zero window probes.
-func TestZeroWindowProbeUserTimeout(t *testing.T) {
- dut := testbench.NewDUT(t)
- listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(t, listenFd)
- conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- defer conn.Close(t)
-
- conn.Connect(t)
- acceptFd, _ := dut.Accept(t, listenFd)
- defer dut.Close(t, acceptFd)
-
- dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
-
- sampleData := []byte("Sample Data")
- samplePayload := &testbench.Payload{Bytes: sampleData}
-
- // Send and receive sample data to the dut.
- dut.Send(t, acceptFd, sampleData, 0)
- if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil {
- t.Fatalf("expected payload was not received: %s", err)
- }
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload)
- if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, nil, time.Second); err != nil {
- t.Fatalf("expected packet was not received: %s", err)
- }
-
- // Test 1: Check for receive of a zero window probe, record the duration for
- // probe to be sent.
- //
- // Advertize zero window to the dut.
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), WindowSize: testbench.Uint16(0)})
-
- // Expected sequence number of the zero window probe.
- probeSeq := testbench.Uint32(uint32(*conn.RemoteSeqNum(t) - 1))
- start := time.Now()
- // Ask the dut to send out data.
- dut.Send(t, acceptFd, sampleData, 0)
- // Expect zero-window probe from the dut.
- if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: probeSeq}, nil, time.Second); err != nil {
- t.Fatalf("expected a packet with sequence number %d: %s", probeSeq, err)
- }
- // Record the duration for first probe, the dut sends the zero window probe after
- // a retransmission time interval.
- startProbeDuration := time.Now().Sub(start)
-
- // Test 2: Check if the dut times out the connection by honoring usertimeout
- // when the dut is sending zero-window probes.
- //
- // Reduce the retransmit timeout.
- dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_USER_TIMEOUT, int32(startProbeDuration.Milliseconds()))
- // Advertize zero window again.
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), WindowSize: testbench.Uint16(0)})
- // Ask the dut to send out data that would trigger zero window probe retransmissions.
- dut.Send(t, acceptFd, sampleData, 0)
-
- // Wait for the connection to timeout after multiple zero-window probe retransmissions.
- time.Sleep(8 * startProbeDuration)
-
- // Expect the connection to have timed out and closed which would cause the dut
- // to reply with a RST to the ACK we send.
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
- if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, nil, time.Second); err != nil {
- t.Fatalf("expected a TCP RST")
- }
-}
diff --git a/test/packetimpact/tests/udp_any_addr_recv_unicast_test.go b/test/packetimpact/tests/udp_any_addr_recv_unicast_test.go
deleted file mode 100644
index f4ae00a81..000000000
--- a/test/packetimpact/tests/udp_any_addr_recv_unicast_test.go
+++ /dev/null
@@ -1,50 +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_any_addr_recv_unicast_test
-
-import (
- "flag"
- "net"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-func TestAnyRecvUnicastUDP(t *testing.T) {
- dut := testbench.NewDUT(t)
- boundFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.IPv4zero)
- defer dut.Close(t, boundFD)
- conn := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
- defer conn.Close(t)
-
- payload := testbench.GenerateRandomPayload(t, 1<<10 /* 1 KiB */)
- conn.SendIP(
- t,
- testbench.IPv4{DstAddr: testbench.Address(tcpip.Address(dut.Net.RemoteIPv4))},
- testbench.UDP{},
- &testbench.Payload{Bytes: payload},
- )
- got, want := dut.Recv(t, boundFD, int32(len(payload)+1), 0), payload
- if diff := cmp.Diff(want, got); diff != "" {
- t.Errorf("received payload does not match sent payload, diff (-want, +got):\n%s", diff)
- }
-}
diff --git a/test/packetimpact/tests/udp_discard_mcast_source_addr_test.go b/test/packetimpact/tests/udp_discard_mcast_source_addr_test.go
deleted file mode 100644
index f63cfcc9a..000000000
--- a/test/packetimpact/tests/udp_discard_mcast_source_addr_test.go
+++ /dev/null
@@ -1,93 +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_discard_mcast_source_addr_test
-
-import (
- "context"
- "flag"
- "fmt"
- "net"
- "testing"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-var oneSecond = unix.Timeval{Sec: 1, Usec: 0}
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-func TestDiscardsUDPPacketsWithMcastSourceAddressV4(t *testing.T) {
- dut := testbench.NewDUT(t)
- remoteFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, dut.Net.RemoteIPv4)
- defer dut.Close(t, remoteFD)
- dut.SetSockOptTimeval(t, remoteFD, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &oneSecond)
- conn := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
- defer conn.Close(t)
-
- for _, mcastAddr := range []net.IP{
- net.IPv4allsys,
- net.IPv4allrouter,
- net.IPv4(224, 0, 1, 42),
- net.IPv4(232, 1, 2, 3),
- } {
- t.Run(fmt.Sprintf("srcaddr=%s", mcastAddr), func(t *testing.T) {
- conn.SendIP(
- t,
- testbench.IPv4{SrcAddr: testbench.Address(tcpip.Address(mcastAddr.To4()))},
- testbench.UDP{},
- &testbench.Payload{Bytes: []byte("test payload")},
- )
-
- ret, payload, errno := dut.RecvWithErrno(context.Background(), t, remoteFD, 100, 0)
- if errno != unix.EAGAIN || errno != unix.EWOULDBLOCK {
- t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, payload, errno)
- }
- })
- }
-}
-
-func TestDiscardsUDPPacketsWithMcastSourceAddressV6(t *testing.T) {
- dut := testbench.NewDUT(t)
- remoteFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, dut.Net.RemoteIPv6)
- defer dut.Close(t, remoteFD)
- dut.SetSockOptTimeval(t, remoteFD, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &oneSecond)
- conn := dut.Net.NewUDPIPv6(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
- defer conn.Close(t)
-
- for _, mcastAddr := range []net.IP{
- net.IPv6interfacelocalallnodes,
- net.IPv6linklocalallnodes,
- net.IPv6linklocalallrouters,
- net.ParseIP("ff01::42"),
- net.ParseIP("ff02::4242"),
- } {
- t.Run(fmt.Sprintf("srcaddr=%s", mcastAddr), func(t *testing.T) {
- conn.SendIPv6(
- t,
- testbench.IPv6{SrcAddr: testbench.Address(tcpip.Address(mcastAddr.To16()))},
- testbench.UDP{},
- &testbench.Payload{Bytes: []byte("test payload")},
- )
- ret, payload, errno := dut.RecvWithErrno(context.Background(), t, remoteFD, 100, 0)
- if errno != unix.EAGAIN || errno != unix.EWOULDBLOCK {
- t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, payload, errno)
- }
- })
- }
-}
diff --git a/test/packetimpact/tests/udp_icmp_error_propagation_test.go b/test/packetimpact/tests/udp_icmp_error_propagation_test.go
deleted file mode 100644
index bb33ca4b3..000000000
--- a/test/packetimpact/tests/udp_icmp_error_propagation_test.go
+++ /dev/null
@@ -1,352 +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_icmp_error_propagation_test
-
-import (
- "context"
- "flag"
- "fmt"
- "net"
- "sync"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-type connectionMode bool
-
-func (c connectionMode) String() string {
- if c {
- return "Connected"
- }
- return "Connectionless"
-}
-
-type icmpError int
-
-const (
- portUnreachable icmpError = iota
- timeToLiveExceeded
-)
-
-func (e icmpError) String() string {
- switch e {
- case portUnreachable:
- return "PortUnreachable"
- case timeToLiveExceeded:
- return "TimeToLiveExpired"
- }
- return "Unknown ICMP error"
-}
-
-func (e icmpError) ToICMPv4(payload []byte) *testbench.ICMPv4 {
- switch e {
- case portUnreachable:
- return &testbench.ICMPv4{
- Type: testbench.ICMPv4Type(header.ICMPv4DstUnreachable),
- Code: testbench.ICMPv4Code(header.ICMPv4PortUnreachable),
- Payload: payload,
- }
- case timeToLiveExceeded:
- return &testbench.ICMPv4{
- Type: testbench.ICMPv4Type(header.ICMPv4TimeExceeded),
- Code: testbench.ICMPv4Code(header.ICMPv4TTLExceeded),
- Payload: payload,
- }
- }
- return nil
-}
-
-type errorDetection struct {
- name string
- useValidConn bool
- f func(context.Context, *testing.T, testData)
-}
-
-type testData struct {
- dut *testbench.DUT
- conn *testbench.UDPIPv4
- remoteFD int32
- remotePort uint16
- cleanFD int32
- cleanPort uint16
- wantErrno unix.Errno
-}
-
-// wantErrno computes the errno to expect given the connection mode of a UDP
-// socket and the ICMP error it will receive.
-func wantErrno(c connectionMode, icmpErr icmpError) unix.Errno {
- if c && icmpErr == portUnreachable {
- return unix.ECONNREFUSED
- }
- return unix.Errno(0)
-}
-
-// sendICMPError sends an ICMP error message in response to a UDP datagram.
-func sendICMPError(t *testing.T, conn *testbench.UDPIPv4, icmpErr icmpError, udp *testbench.UDP) {
- t.Helper()
-
- ip, ok := udp.Prev().(*testbench.IPv4)
- if !ok {
- t.Fatalf("expected %s to be IPv4", udp.Prev())
- }
- if icmpErr == timeToLiveExceeded {
- *ip.TTL = 1
- // Let serialization recalculate the checksum since we set the TTL
- // to 1.
- ip.Checksum = nil
- }
-
- icmpPayload := testbench.Layers{ip, udp}
- bytes, err := icmpPayload.ToBytes()
- if err != nil {
- t.Fatalf("got icmpPayload.ToBytes() = (_, %s), want = (_, nil)", err)
- }
-
- layers := conn.CreateFrame(t, nil)
- layers[len(layers)-1] = icmpErr.ToICMPv4(bytes)
- conn.SendFrameStateless(t, layers)
-}
-
-// testRecv tests observing the ICMP error through the recv unix. A packet
-// is sent to the DUT, and if wantErrno is non-zero, then the first recv should
-// fail and the second should succeed. Otherwise if wantErrno is zero then the
-// first recv should succeed immediately.
-func testRecv(ctx context.Context, t *testing.T, d testData) {
- t.Helper()
-
- // Check that receiving on the clean socket works.
- d.conn.Send(t, testbench.UDP{DstPort: &d.cleanPort})
- d.dut.Recv(t, d.cleanFD, 100, 0)
-
- d.conn.Send(t, testbench.UDP{})
-
- if d.wantErrno != unix.Errno(0) {
- ret, _, err := d.dut.RecvWithErrno(ctx, t, d.remoteFD, 100, 0)
- if ret != -1 {
- t.Fatalf("recv after ICMP error succeeded unexpectedly, expected (%[1]d) %[1]v", d.wantErrno)
- }
- if err != d.wantErrno {
- t.Fatalf("recv after ICMP error resulted in error (%[1]d) %[1]v, expected (%[2]d) %[2]v", err, d.wantErrno)
- }
- }
-
- d.dut.Recv(t, d.remoteFD, 100, 0)
-}
-
-// testSendTo tests observing the ICMP error through the send syscall. If
-// wantErrno is non-zero, the first send should fail and a subsequent send
-// should suceed; while if wantErrno is zero then the first send should just
-// succeed.
-func testSendTo(ctx context.Context, t *testing.T, d testData) {
- // Check that sending on the clean socket works.
- d.dut.SendTo(t, d.cleanFD, nil, 0, d.conn.LocalAddr(t))
- if _, err := d.conn.Expect(t, testbench.UDP{SrcPort: &d.cleanPort}, time.Second); err != nil {
- t.Fatalf("did not receive UDP packet from clean socket on DUT: %s", err)
- }
-
- if d.wantErrno != unix.Errno(0) {
- ret, err := d.dut.SendToWithErrno(ctx, t, d.remoteFD, nil, 0, d.conn.LocalAddr(t))
-
- if ret != -1 {
- t.Fatalf("sendto after ICMP error succeeded unexpectedly, expected (%[1]d) %[1]v", d.wantErrno)
- }
- if err != d.wantErrno {
- t.Fatalf("sendto after ICMP error resulted in error (%[1]d) %[1]v, expected (%[2]d) %[2]v", err, d.wantErrno)
- }
- }
-
- d.dut.SendTo(t, d.remoteFD, nil, 0, d.conn.LocalAddr(t))
- if _, err := d.conn.Expect(t, testbench.UDP{}, time.Second); err != nil {
- t.Fatalf("did not receive UDP packet as expected: %s", err)
- }
-}
-
-func testSockOpt(_ context.Context, t *testing.T, d testData) {
- // Check that there's no pending error on the clean socket.
- if errno := unix.Errno(d.dut.GetSockOptInt(t, d.cleanFD, unix.SOL_SOCKET, unix.SO_ERROR)); errno != unix.Errno(0) {
- t.Fatalf("unexpected error (%[1]d) %[1]v on clean socket", errno)
- }
-
- if errno := unix.Errno(d.dut.GetSockOptInt(t, d.remoteFD, unix.SOL_SOCKET, unix.SO_ERROR)); errno != d.wantErrno {
- t.Fatalf("SO_ERROR sockopt after ICMP error is (%[1]d) %[1]v, expected (%[2]d) %[2]v", errno, d.wantErrno)
- }
-
- // Check that after clearing socket error, sending doesn't fail.
- d.dut.SendTo(t, d.remoteFD, nil, 0, d.conn.LocalAddr(t))
- if _, err := d.conn.Expect(t, testbench.UDP{}, time.Second); err != nil {
- t.Fatalf("did not receive UDP packet as expected: %s", err)
- }
-}
-
-// TestUDPICMPErrorPropagation tests that ICMP error messages in response to
-// UDP datagrams are processed correctly. RFC 1122 section 4.1.3.3 states that:
-// "UDP MUST pass to the application layer all ICMP error messages that it
-// receives from the IP layer."
-//
-// The test cases are parametrized in 3 dimensions: 1. the UDP socket is either
-// put into connection mode or left connectionless, 2. the ICMP message type
-// and code, and 3. the method by which the ICMP error is observed on the
-// socket: sendto, recv, or getsockopt(SO_ERROR).
-//
-// Linux's udp(7) man page states: "All fatal errors will be passed to the user
-// as an error return even when the socket is not connected. This includes
-// asynchronous errors received from the network." In practice, the only
-// combination of parameters to the test that causes an error to be observable
-// on the UDP socket is receiving a port unreachable message on a connected
-// socket.
-func TestUDPICMPErrorPropagation(t *testing.T) {
- for _, connect := range []connectionMode{true, false} {
- for _, icmpErr := range []icmpError{portUnreachable, timeToLiveExceeded} {
- wantErrno := wantErrno(connect, icmpErr)
-
- for _, errDetect := range []errorDetection{
- {"SendTo", false, testSendTo},
- // Send to an address that's different from the one that caused an ICMP
- // error to be returned.
- {"SendToValid", true, testSendTo},
- {"Recv", false, testRecv},
- {"SockOpt", false, testSockOpt},
- } {
- t.Run(fmt.Sprintf("%s/%s/%s", connect, icmpErr, errDetect.name), func(t *testing.T) {
- dut := testbench.NewDUT(t)
-
- remoteFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.IPv4zero)
- defer dut.Close(t, remoteFD)
-
- // Create a second, clean socket on the DUT to ensure that the ICMP
- // error messages only affect the sockets they are intended for.
- cleanFD, cleanPort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.IPv4zero)
- defer dut.Close(t, cleanFD)
-
- conn := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
- defer conn.Close(t)
-
- if connect {
- dut.Connect(t, remoteFD, conn.LocalAddr(t))
- dut.Connect(t, cleanFD, conn.LocalAddr(t))
- }
-
- dut.SendTo(t, remoteFD, nil, 0, conn.LocalAddr(t))
- udp, err := conn.Expect(t, testbench.UDP{}, time.Second)
- if err != nil {
- t.Fatalf("did not receive message from DUT: %s", err)
- }
-
- sendICMPError(t, &conn, icmpErr, udp)
-
- errDetectConn := &conn
- if errDetect.useValidConn {
- // connClean is a UDP socket on the test runner that was not
- // involved in the generation of the ICMP error. As such,
- // interactions between it and the the DUT should be independent of
- // the ICMP error at least at the port level.
- connClean := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
- defer connClean.Close(t)
-
- errDetectConn = &connClean
- }
-
- errDetect.f(context.Background(), t, testData{&dut, errDetectConn, remoteFD, remotePort, cleanFD, cleanPort, wantErrno})
- })
- }
- }
- }
-}
-
-// TestICMPErrorDuringUDPRecv tests behavior when a UDP socket is in the middle
-// of a blocking recv and receives an ICMP error.
-func TestICMPErrorDuringUDPRecv(t *testing.T) {
- for _, connect := range []connectionMode{true, false} {
- for _, icmpErr := range []icmpError{portUnreachable, timeToLiveExceeded} {
- wantErrno := wantErrno(connect, icmpErr)
-
- t.Run(fmt.Sprintf("%s/%s", connect, icmpErr), func(t *testing.T) {
- dut := testbench.NewDUT(t)
-
- remoteFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.IPv4zero)
- defer dut.Close(t, remoteFD)
-
- // Create a second, clean socket on the DUT to ensure that the ICMP
- // error messages only affect the sockets they are intended for.
- cleanFD, cleanPort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.IPv4zero)
- defer dut.Close(t, cleanFD)
-
- conn := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
- defer conn.Close(t)
-
- if connect {
- dut.Connect(t, remoteFD, conn.LocalAddr(t))
- dut.Connect(t, cleanFD, conn.LocalAddr(t))
- }
-
- dut.SendTo(t, remoteFD, nil, 0, conn.LocalAddr(t))
- udp, err := conn.Expect(t, testbench.UDP{}, time.Second)
- if err != nil {
- t.Fatalf("did not receive message from DUT: %s", err)
- }
-
- var wg sync.WaitGroup
- wg.Add(2)
- go func() {
- defer wg.Done()
-
- if wantErrno != unix.Errno(0) {
- ret, _, err := dut.RecvWithErrno(context.Background(), t, remoteFD, 100, 0)
- if ret != -1 {
- t.Errorf("recv during ICMP error succeeded unexpectedly, expected (%[1]d) %[1]v", wantErrno)
- return
- }
- if err != wantErrno {
- t.Errorf("recv during ICMP error resulted in error (%[1]d) %[1]v, expected (%[2]d) %[2]v", err, wantErrno)
- return
- }
- }
-
- if ret, _, err := dut.RecvWithErrno(context.Background(), t, remoteFD, 100, 0); ret == -1 {
- t.Errorf("recv after ICMP error failed with (%[1]d) %[1]", err)
- }
- }()
-
- go func() {
- defer wg.Done()
-
- if ret, _, err := dut.RecvWithErrno(context.Background(), t, cleanFD, 100, 0); ret == -1 {
- t.Errorf("recv on clean socket failed with (%[1]d) %[1]", err)
- }
- }()
-
- // TODO(b/155684889) This sleep is to allow time for the DUT to
- // actually call recv since we want the ICMP error to arrive during the
- // blocking recv, and should be replaced when a better synchronization
- // alternative is available.
- time.Sleep(2 * time.Second)
-
- sendICMPError(t, &conn, icmpErr, udp)
-
- conn.Send(t, testbench.UDP{DstPort: &cleanPort})
- conn.Send(t, testbench.UDP{})
- wg.Wait()
- })
- }
- }
-}