diff options
Diffstat (limited to 'test/packetimpact')
59 files changed, 0 insertions, 14425 deletions
diff --git a/test/packetimpact/README.md b/test/packetimpact/README.md deleted file mode 100644 index 3f0f9d155..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. -* **Flexibility** 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 framework 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: ðerType}, 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 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 -} -``` - -`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 f27e52f93..000000000 --- a/test/packetimpact/runner/dut.go +++ /dev/null @@ -1,663 +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) - } - }) - } - - // Arm the cleanup hook before we do anthing else. Otherwise failures below - // can cause the test to hang in the cleanup hook above. - 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) - } - }) - - for _, info := range dutInfos { - n := info.Net - // 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) - } - } - } - - // 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 ðer, 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{ðerState{}}, - } - 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 ðer -} - -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 e124002f6..000000000 --- a/test/packetimpact/tests/tcp_listen_backlog_test.go +++ /dev/null @@ -1,308 +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 ( - "bytes" - "flag" - "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) -} - -// 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) - - // This is the number of pending connections before SYN cookies are used. - const backlog = 10 - - listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, backlog) - defer dut.Close(t, listenFd) - - // Fill the SYN queue with connections in SYN-RCVD. We will use these to test - // that ACKs received while the accept queue is full are ignored. - var synQueueConns [backlog]testbench.TCPIPv4 - defer func() { - for i := range synQueueConns { - synQueueConns[i].Close(t) - } - }() - { - var wg sync.WaitGroup - for i := range synQueueConns { - conn := &synQueueConns[i] - *conn = dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{}) - - wg.Add(1) - go func(i int) { - defer wg.Done() - - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}) - if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err != nil { - t.Errorf("%d: expected TCP frame: %s", i, err) - } else if got, want := *got.Flags, header.TCPFlagSyn|header.TCPFlagAck; got != want { - t.Errorf("%d: got %s, want %s", i, got, want) - } - }(i) - } - wg.Wait() - if t.Failed() { - t.FailNow() - } - } - - const payloadLen = 1 - payload := testbench.Payload{Bytes: testbench.GenerateRandomPayload(t, payloadLen)} - - // Fill the accept queue with connections established using SYN cookies. - var synCookieConns [backlog + 1]testbench.TCPIPv4 - defer func() { - for i := range synCookieConns { - synCookieConns[i].Close(t) - } - }() - { - var wg sync.WaitGroup - for i := range synCookieConns { - conn := &synCookieConns[i] - *conn = dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{}) - - wg.Add(1) - go func(i int) { - defer wg.Done() - - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}) - if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err != nil { - t.Errorf("%d: expected TCP frame: %s", i, err) - } else if got, want := *got.Flags, header.TCPFlagSyn|header.TCPFlagAck; got != want { - t.Errorf("%d: got %s, want %s", i, got, want) - } - // Send a payload so we can observe the dut ACK. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, &payload) - if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err != nil { - t.Errorf("%d: expected TCP frame: %s", i, err) - } else if got, want := *got.Flags, header.TCPFlagAck; got != want { - t.Errorf("%d: got %s, want %s", i, got, want) - } - }(i) - } - wg.Wait() - if t.Failed() { - t.FailNow() - } - } - - // Send ACKs to complete the handshakes. These are expected to be dropped - // because the accept queue is full. - { - var wg sync.WaitGroup - for i := range synQueueConns { - conn := &synQueueConns[i] - wg.Add(1) - go func(i int) { - defer wg.Done() - - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - // Wait for the SYN-ACK to be retransmitted to confirm the ACK was - // dropped. - seqNum := uint32(*conn.RemoteSeqNum(t) - 1) - if got, err := conn.Expect(t, testbench.TCP{SeqNum: &seqNum}, time.Second); err != nil { - t.Errorf("%d: expected TCP frame: %s", i, err) - } else if got, want := *got.Flags, header.TCPFlagSyn|header.TCPFlagAck; got != want { - t.Errorf("%d: got %s, want %s", i, got, want) - } - }(i) - } - - wg.Wait() - if t.Failed() { - t.FailNow() - } - } - - // While the accept queue is still full, send an unexpected ACK from a new - // socket. The listener should reply with an RST. - func() { - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{}) - defer conn.Close(t) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err != nil { - t.Errorf("expected TCP frame: %s", err) - } else if got, want := *got.Flags, header.TCPFlagRst; got != want { - t.Errorf("got %s, want %s", got, want) - } - }() - - func() { - // Now initiate a new connection when the accept queue is full. - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{}) - defer conn.Close(t) - // Expect dut connection to drop the SYN. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}) - if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err == nil { - t.Fatalf("expected no TCP frame, got %s", got) - } - }() - - // Drain the accept queue. - { - var wg sync.WaitGroup - for i := range synCookieConns { - conn := &synCookieConns[i] - - wg.Add(1) - go func(i int) { - defer wg.Done() - - fd, _ := dut.Accept(t, listenFd) - b := dut.Recv(t, fd, payloadLen+1, 0) - dut.Close(t, fd) - if !bytes.Equal(b, payload.Bytes) { - t.Errorf("connection %d: got dut.Recv = %x, want = %x", i, b, payload.Bytes) - } - - if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err != nil { - t.Errorf("%d: expected TCP frame: %s", i, err) - } else if got, want := *got.Flags, header.TCPFlagFin|header.TCPFlagAck; got != want { - t.Errorf("%d: got %s, want %s", i, got, want) - } - - // Prevent retransmission. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - }(i) - } - wg.Wait() - if t.Failed() { - t.FailNow() - } - } - - // Complete the partial connections to move them from the SYN queue to the - // accept queue. We will use these to test that connections in the accept - // queue are closed on listener shutdown. - { - var wg sync.WaitGroup - for i := range synQueueConns { - conn := &synQueueConns[i] - wg.Add(1) - go func(i int) { - defer wg.Done() - - tcp := testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)} - - // Exercise connections with and without pending data. - if i%2 == 0 { - // Send ACK with no payload; wait for absence of SYN-ACK retransmit. - conn.Send(t, tcp) - if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err == nil { - t.Errorf("%d: expected no TCP frame, got %s", i, got) - } - } else { - // Send ACK with payload; wait for ACK. - conn.Send(t, tcp, &payload) - if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err != nil { - t.Errorf("%d: expected TCP frame: %s", i, err) - } else if got, want := *got.Flags, header.TCPFlagAck; got != want { - t.Errorf("%d: got %s, want %s", i, got, want) - } - } - }(i) - } - - wg.Wait() - if t.Failed() { - t.FailNow() - } - } - - // The accept queue now has N-1 connections in it. The next incoming SYN will - // enter the SYN queue, and the one following will use SYN cookies. We test - // both. - var connectingConns [2]testbench.TCPIPv4 - defer func() { - for i := range connectingConns { - connectingConns[i].Close(t) - } - }() - { - var wg sync.WaitGroup - for i := range connectingConns { - conn := &connectingConns[i] - *conn = dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{}) - - wg.Add(1) - go func(i int) { - defer wg.Done() - - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}) - if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err != nil { - t.Errorf("%d: expected TCP frame: %s", i, err) - } else if got, want := *got.Flags, header.TCPFlagSyn|header.TCPFlagAck; got != want { - t.Errorf("%d: got %s, want %s", i, got, want) - } - }(i) - } - wg.Wait() - if t.Failed() { - t.FailNow() - } - } - - dut.Shutdown(t, listenFd, unix.SHUT_RD) - - var wg sync.WaitGroup - - // Shutdown causes Connections in the accept queue to be closed. - for i := range synQueueConns { - conn := &synQueueConns[i] - wg.Add(1) - go func(i int) { - defer wg.Done() - - if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err != nil { - t.Errorf("%d: expected TCP frame: %s", i, err) - } else if got, want := *got.Flags, header.TCPFlagRst|header.TCPFlagAck; got != want { - t.Errorf("%d: got %s, want %s", i, got, want) - } - }(i) - } - - for i := range connectingConns { - conn := &connectingConns[i] - - wg.Add(1) - go func(i int) { - defer wg.Done() - - if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err == nil { - t.Errorf("%d: expected no TCP frame, got %s", i, got) - } - }(i) - } - - wg.Wait() -} 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 556cee1d9..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 succeed; 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() - }) - } - } -} |