diff options
Diffstat (limited to 'test/packetimpact')
21 files changed, 0 insertions, 3734 deletions
diff --git a/test/packetimpact/README.md b/test/packetimpact/README.md deleted file mode 100644 index ece4dedc6..000000000 --- a/test/packetimpact/README.md +++ /dev/null @@ -1,531 +0,0 @@ -# Packetimpact - -## What is packetimpact? - -Packetimpact is a tool for platform-independent network testing. It is heavily -inspired by [packetdrill](https://github.com/google/packetdrill). It creates two -docker containers connected by a network. One is for the test bench, which -operates the test. The other is for the device-under-test (DUT), which is the -software being tested. The test bench communicates over the network with the DUT -to check correctness of the network. - -### Goals - -Packetimpact aims to provide: - -* A **multi-platform** solution that can test both Linux and gVisor. -* **Conciseness** on par with packetdrill scripts. -* **Control-flow** like for loops, conditionals, and variables. -* **Flexibilty** to specify every byte in a packet or use multiple sockets. - -## When to use packetimpact? - -There are a few ways to write networking tests for gVisor currently: - -* [Go unit tests](https://github.com/google/gvisor/tree/master/pkg/tcpip) -* [syscall tests](https://github.com/google/gvisor/tree/master/test/syscalls/linux) -* [packetdrill tests](https://github.com/google/gvisor/tree/master/test/packetdrill) -* packetimpact tests - -The right choice depends on the needs of the test. - -Feature | Go unit test | syscall test | packetdrill | packetimpact -------------- | ------------ | ------------ | ----------- | ------------ -Multiplatform | 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 multiplatform**. If you require insight on gVisor -internals, this is the right choice. - -### Syscall tests - -Syscall tests are **multiplatform** 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 **multiplatform** 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 -**multiplatform** 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 - -``` - +--------------+ +--------------+ - | | TEST NET | | - | | <===========> | Device | - | Test | | Under | - | Bench | | Test | - | | <===========> | (DUT) | - | | CONTROL NET | | - +--------------+ +--------------+ -``` - -Two docker containers are created by a script, one for the test bench and the -other for the device under test (DUT). The script connects the two containers -with a control network and test network. It also does some other tasks like -waiting until the DUT is ready before starting the test and disabling Linux -networking that would interfere with the test bench. - -### DUT - -The DUT container runs a program called the "posix_server". The posix_server is -written in c++ for maximum portability. It is compiled on the host. The script -that starts the containers copies it into the DUT's container and runs it. It's -job is to receive directions from the test bench on what actions to take. For -this, the posix_server does three steps in a loop: - -1. Listen for a request from the test bench. -2. Execute a command. -3. Send the response back to the test bench. - -The requests and responses are -[protobufs](https://developers.google.com/protocol-buffers) and the -communication is done with [gRPC](https://grpc.io/). The commands run are -[POSIX socket commands](https://en.wikipedia.org/wiki/Berkeley_sockets#Socket_API_functions), -with the inputs and outputs converted into protobuf requests and responses. All -communication is on the control network, so that the test network is unaffected -by extra packets. - -For example, this is the request and response pair to call -[`socket()`](http://man7.org/linux/man-pages/man2/socket.2.html): - -```protocol-buffer -message SocketRequest { - int32 domain = 1; - int32 type = 2; - int32 protocol = 3; -} - -message SocketResponse { - int32 fd = 1; - int32 errno_ = 2; -} -``` - -##### Alternatives considered - -* We could have use JSON for communication instead. It would have been a - lighter-touch than protobuf but protobuf handles all the data type and has - strict typing to prevent a class of errors. The test bench could be written - in other languages, too. -* Instead of mimicking the POSIX interfaces, arguments could have had a more - natural form, like the `bind()` getting a string IP address instead of bytes - in a `sockaddr_t`. However, conforming to the existing structures keeps more - of the complexity in Go and keeps the posix_server simpler and thus more - likely to compile everywhere. - -### Test Bench - -The test bench does most of the work in a test. It is a Go program that compiles -on the host and is copied by the script into test bench's container. It is a -regular [go unit test](https://golang.org/pkg/testing/) that imports the test -bench framework. The test bench framwork is based on three basic utilities: - -* Commanding the DUT to run POSIX commands and return responses. -* Sending raw packets to the DUT on the test network. -* Listening for raw packets from the DUT on the test network. - -#### DUT commands - -To keep the interface to the DUT consistent and easy-to-use, each POSIX command -supported by the posix_server is wrapped in functions with signatures similar to -the ones in the [Go unix package](https://godoc.org/golang.org/x/sys/unix). This -way all the details of endianess and (un)marshalling of go structs such as -[unix.Timeval](https://godoc.org/golang.org/x/sys/unix#Timeval) is handled in -one place. This also makes it straight-forward to convert tests that use `unix.` -or `syscall.` calls to `dut.` calls. - -For example, creating a connection to the DUT and commanding it to make a socket -looks like this: - -```go -dut := testbench.NewDut(t) -fd, err := dut.SocketWithErrno(unix.AF_INET, unix.SOCK_STREAM, unix.IPPROTO_IP) -if fd < 0 { - t.Fatalf(...) -} -``` - -Because the usual case is to fail the test when the DUT fails to create a -socket, there is a concise version of each of the `...WithErrno` functions that -does that: - -```go -dut := testbench.NewDut(t) -fd := dut.Socket(unix.AF_INET, unix.SOCK_STREAM, unix.IPPROTO_IP) -``` - -The DUT and other structs in the code store a `*testing.T` so that they can -provide versions of functions that call `t.Fatalf(...)`. This helps keep tests -concise. - -##### Alternatives considered - -* Instead of mimicking the `unix.` go interface, we could have invented a more - natural one, like using `float64` instead of `Timeval`. However, using the - same function signatures that `unix.` has makes it easier to convert code to - `dut.`. Also, using an existing interface ensures that we don't invent an - interface that isn't extensible. For example, if we invented a function for - `bind()` that didn't support IPv6 and later we had to add a second `bind6()` - function. - -#### Sending/Receiving Raw Packets - -The framework wraps POSIX sockets for sending and receiving raw frames. Both -send and receive are synchronous commands. -[SO_RCVTIMEO](http://man7.org/linux/man-pages/man7/socket.7.html) is used to set -a timeout on the receive commands. For ease of use, these are wrapped in an -`Injector` and a `Sniffer`. They have functions: - -```go -func (s *Sniffer) Recv(timeout time.Duration) []byte {...} -func (i *Injector) Send(b []byte) {...} -``` - -##### Alternatives considered - -* [gopacket](https://github.com/google/gopacket) pcap has raw socket support - but requires cgo. cgo is not guaranteed to be portable from the host to the - container and in practice, the container doesn't recognize binaries built on - the host if they use cgo. -* Both gVisor and gopacket have the ability to read and write pcap files - without cgo but that is insufficient here. -* 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 a one-line call to - `mergo.Merge(...)`. 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. Each override is - specified as just one call to `mergo.Merge`. - * 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) -} -``` - -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 aroung 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. - -#### Connections - -Using `Layers` above, we can create connection structures to maintain state -about connections. For example, here is the `TCPIPv4` struct: - -``` -type TCPIPv4 struct { - outgoing Layers - incoming Layers - localSeqNum uint32 - remoteSeqNum uint32 - sniffer Sniffer - injector Injector - t *testing.T -} -``` - -`TCPIPv4` contains an `outgoing Layers` which holds the defaults for the -connection, such as the source and destination MACs, IPs, and ports. When -`outgoing.toBytes()` is called a valid packet for this TCPIPv4 flow is built. - -It also contains `incoming Layers` which holds filter for incoming packets that -belong to this flow. `incoming.match(Layers)` is used on received bytes to check -if they are part of the flow. - -The `sniffer` and `injector` are for receiving and sending raw packet bytes. The -`localSeqNum` and `remoteSeqNum` are updated by `Send` and `Recv` so that -outgoing packets will have, by default, the correct sequence number and ack -number. - -TCPIPv4 provides some functions: - -``` -func (conn *TCPIPv4) Send(tcp TCP) {...} -func (conn *TCPIPv4) Recv(timeout time.Duration) *TCP {...} -``` - -`Send(tcp TCP)` uses [mergo](https://github.com/imdario/mergo) to merge the -provided `TCP` (a `Layer`) into `outgoing`. This way the user can specify -concisely just which fields of `outgoing` to modify. The packet is sent using -the `injector`. - -`Recv(timeout time.Duration)` reads packets from the sniffer until either the -timeout has elapsed or a packet that matches `incoming` arrives. - -Using those, we can perform a TCP 3-way handshake without too much code: - -```go -func (conn *TCPIPv4) Handshake() { - syn := uint8(header.TCPFlagSyn) - synack := uint8(header.TCPFlagSyn) - ack := uint8(header.TCPFlagAck) - conn.Send(TCP{Flags: &syn}) // Send a packet with all defaults but set TCP-SYN. - - // Wait for the SYN-ACK response. - for { - newTCP := conn.Recv(time.Second) // This already filters by MAC, IP, and ports. - if TCP{Flags: &synack}.match(newTCP) { - break // Only if it's a SYN-ACK proceed. - } - } - - conn.Send(TCP{Flags: &ack}) // Send an ACK. The seq and ack numbers are set correctly. -} -``` - -The handshake code is part of the testbench utilities so tests can share this -common sequence, making tests even more concise. - -##### Alternatives considered - -* Instead of storing `outgoing` and `incoming`, store values. - * There would be many more things to store instead, like `localMac`, - `remoteMac`, `localIP`, `remoteIP`, `localPort`, and `remotePort`. - * Construction of a packet would be many lines to copy each of these - values into a `[]byte`. And there would be slight variations needed for - each encapsulation stack, like TCPIPv6 and ARP. - * Filtering incoming packets would be a long sequence: - * Compare the MACs, then - * Parse the next header, then - * Compare the IPs, then - * Parse the next header, then - * Compare the TCP ports. Instead it's all just one call to - `cmp.Equal(...)`, for all sequences. - * A TCPIPv6 connection could share most of the code. Only the type of the - IP addresses are different. The types of `outgoing` and `incoming` would - be remain `Layers`. - * An ARP connection could share all the Ethernet parts. The IP `Layer` - could be factored out of `outgoing`. After that, the IPv4 and IPv6 - connections could implement one interface and a single TCP struct could - have either network protocol through composition. - -## Putting it all together - -Here's what te start of a packetimpact unit test looks like. This test creates a -TCP connection with the DUT. There are added comments for explanation in this -document but a real test might not include them in order to stay even more -concise. - -```go -func TestMyTcpTest(t *testing.T) { - // Prepare a DUT for communication. - dut := testbench.NewDUT(t) - - // This does: - // dut.Socket() - // dut.Bind() - // dut.Getsockname() to learn the new port number - // dut.Listen() - listenFD, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(listenFD) // Tell the DUT to close the socket at the end of the test. - - // Monitor a new TCP connection with sniffer, injector, sequence number tracking, - // and reasonable outgoing and incoming packet field default IPs, MACs, and port numbers. - conn := testbench.NewTCPIPv4(t, dut, remotePort) - - // Perform a 3-way handshake: send SYN, expect SYNACK, send ACK. - conn.Handshake() - - // Tell the DUT to accept the new connection. - acceptFD := dut.Accept(acceptFd) -} -``` - -## Other notes - -* The time between receiving a SYN-ACK and replying with an ACK in `Handshake` - is about 3ms. This is much slower than the native unix response, which is - about 0.3ms. Packetdrill gets closer to 0.3ms. For tests where timing is - crucial, packetdrill is faster and more precise. diff --git a/test/packetimpact/dut/BUILD b/test/packetimpact/dut/BUILD deleted file mode 100644 index 3ce63c2c6..000000000 --- a/test/packetimpact/dut/BUILD +++ /dev/null @@ -1,18 +0,0 @@ -load("//tools:defs.bzl", "cc_binary", "grpcpp") - -package( - default_visibility = ["//test/packetimpact:__subpackages__"], - licenses = ["notice"], -) - -cc_binary( - name = "posix_server", - srcs = ["posix_server.cc"], - linkstatic = 1, - static = True, # This is needed for running in a docker container. - deps = [ - grpcpp, - "//test/packetimpact/proto:posix_server_cc_grpc_proto", - "//test/packetimpact/proto:posix_server_cc_proto", - ], -) diff --git a/test/packetimpact/dut/posix_server.cc b/test/packetimpact/dut/posix_server.cc deleted file mode 100644 index 86e580c6f..000000000 --- a/test/packetimpact/dut/posix_server.cc +++ /dev/null @@ -1,260 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <fcntl.h> -#include <getopt.h> -#include <netdb.h> -#include <netinet/in.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <unistd.h> - -#include <iostream> -#include <unordered_map> - -#include "arpa/inet.h" -#include "include/grpcpp/security/server_credentials.h" -#include "include/grpcpp/server_builder.h" -#include "test/packetimpact/proto/posix_server.grpc.pb.h" -#include "test/packetimpact/proto/posix_server.pb.h" - -// Converts a sockaddr_storage to a Sockaddr message. -::grpc::Status sockaddr_to_proto(const sockaddr_storage &addr, - socklen_t addrlen, - posix_server::Sockaddr *sockaddr_proto) { - switch (addr.ss_family) { - case AF_INET: { - auto addr_in = reinterpret_cast<const sockaddr_in *>(&addr); - auto response_in = sockaddr_proto->mutable_in(); - response_in->set_family(addr_in->sin_family); - response_in->set_port(ntohs(addr_in->sin_port)); - response_in->mutable_addr()->assign( - reinterpret_cast<const char *>(&addr_in->sin_addr.s_addr), 4); - return ::grpc::Status::OK; - } - case AF_INET6: { - auto addr_in6 = reinterpret_cast<const sockaddr_in6 *>(&addr); - auto response_in6 = sockaddr_proto->mutable_in6(); - response_in6->set_family(addr_in6->sin6_family); - response_in6->set_port(ntohs(addr_in6->sin6_port)); - response_in6->set_flowinfo(ntohl(addr_in6->sin6_flowinfo)); - response_in6->mutable_addr()->assign( - reinterpret_cast<const char *>(&addr_in6->sin6_addr.s6_addr), 16); - response_in6->set_scope_id(ntohl(addr_in6->sin6_scope_id)); - return ::grpc::Status::OK; - } - } - return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "Unknown Sockaddr"); -} - -class PosixImpl final : public posix_server::Posix::Service { - ::grpc::Status Accept(grpc_impl::ServerContext *context, - const ::posix_server::AcceptRequest *request, - ::posix_server::AcceptResponse *response) override { - sockaddr_storage addr; - socklen_t addrlen = sizeof(addr); - response->set_fd(accept(request->sockfd(), - reinterpret_cast<sockaddr *>(&addr), &addrlen)); - response->set_errno_(errno); - return sockaddr_to_proto(addr, addrlen, response->mutable_addr()); - } - - ::grpc::Status Bind(grpc_impl::ServerContext *context, - const ::posix_server::BindRequest *request, - ::posix_server::BindResponse *response) override { - if (!request->has_addr()) { - return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, - "Missing address"); - } - sockaddr_storage addr; - - switch (request->addr().sockaddr_case()) { - case posix_server::Sockaddr::SockaddrCase::kIn: { - auto request_in = request->addr().in(); - if (request_in.addr().size() != 4) { - return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, - "IPv4 address must be 4 bytes"); - } - auto addr_in = reinterpret_cast<sockaddr_in *>(&addr); - addr_in->sin_family = request_in.family(); - addr_in->sin_port = htons(request_in.port()); - request_in.addr().copy( - reinterpret_cast<char *>(&addr_in->sin_addr.s_addr), 4); - break; - } - case posix_server::Sockaddr::SockaddrCase::kIn6: { - auto request_in6 = request->addr().in6(); - if (request_in6.addr().size() != 16) { - return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, - "IPv6 address must be 16 bytes"); - } - auto addr_in6 = reinterpret_cast<sockaddr_in6 *>(&addr); - addr_in6->sin6_family = request_in6.family(); - addr_in6->sin6_port = htons(request_in6.port()); - addr_in6->sin6_flowinfo = htonl(request_in6.flowinfo()); - request_in6.addr().copy( - reinterpret_cast<char *>(&addr_in6->sin6_addr.s6_addr), 16); - addr_in6->sin6_scope_id = htonl(request_in6.scope_id()); - break; - } - case posix_server::Sockaddr::SockaddrCase::SOCKADDR_NOT_SET: - default: - return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, - "Unknown Sockaddr"); - } - response->set_ret(bind(request->sockfd(), - reinterpret_cast<sockaddr *>(&addr), sizeof(addr))); - response->set_errno_(errno); - return ::grpc::Status::OK; - } - - ::grpc::Status Close(grpc_impl::ServerContext *context, - const ::posix_server::CloseRequest *request, - ::posix_server::CloseResponse *response) override { - response->set_ret(close(request->fd())); - response->set_errno_(errno); - return ::grpc::Status::OK; - } - - ::grpc::Status GetSockName( - grpc_impl::ServerContext *context, - const ::posix_server::GetSockNameRequest *request, - ::posix_server::GetSockNameResponse *response) override { - sockaddr_storage addr; - socklen_t addrlen = sizeof(addr); - response->set_ret(getsockname( - request->sockfd(), reinterpret_cast<sockaddr *>(&addr), &addrlen)); - response->set_errno_(errno); - return sockaddr_to_proto(addr, addrlen, response->mutable_addr()); - } - - ::grpc::Status Listen(grpc_impl::ServerContext *context, - const ::posix_server::ListenRequest *request, - ::posix_server::ListenResponse *response) override { - response->set_ret(listen(request->sockfd(), request->backlog())); - response->set_errno_(errno); - return ::grpc::Status::OK; - } - - ::grpc::Status Send(::grpc::ServerContext *context, - const ::posix_server::SendRequest *request, - ::posix_server::SendResponse *response) override { - response->set_ret(::send(request->sockfd(), request->buf().data(), - request->buf().size(), request->flags())); - response->set_errno_(errno); - return ::grpc::Status::OK; - } - - ::grpc::Status SetSockOpt( - grpc_impl::ServerContext *context, - const ::posix_server::SetSockOptRequest *request, - ::posix_server::SetSockOptResponse *response) override { - response->set_ret(setsockopt(request->sockfd(), request->level(), - request->optname(), request->optval().c_str(), - request->optval().size())); - response->set_errno_(errno); - return ::grpc::Status::OK; - } - - ::grpc::Status SetSockOptInt( - ::grpc::ServerContext *context, - const ::posix_server::SetSockOptIntRequest *request, - ::posix_server::SetSockOptIntResponse *response) override { - int opt = request->intval(); - response->set_ret(::setsockopt(request->sockfd(), request->level(), - request->optname(), &opt, sizeof(opt))); - response->set_errno_(errno); - return ::grpc::Status::OK; - } - - ::grpc::Status SetSockOptTimeval( - ::grpc::ServerContext *context, - const ::posix_server::SetSockOptTimevalRequest *request, - ::posix_server::SetSockOptTimevalResponse *response) override { - timeval tv = {.tv_sec = static_cast<__time_t>(request->timeval().seconds()), - .tv_usec = static_cast<__suseconds_t>( - request->timeval().microseconds())}; - response->set_ret(setsockopt(request->sockfd(), request->level(), - request->optname(), &tv, sizeof(tv))); - response->set_errno_(errno); - return ::grpc::Status::OK; - } - - ::grpc::Status Socket(grpc_impl::ServerContext *context, - const ::posix_server::SocketRequest *request, - ::posix_server::SocketResponse *response) override { - response->set_fd( - socket(request->domain(), request->type(), request->protocol())); - response->set_errno_(errno); - return ::grpc::Status::OK; - } - - ::grpc::Status Recv(::grpc::ServerContext *context, - const ::posix_server::RecvRequest *request, - ::posix_server::RecvResponse *response) override { - std::vector<char> buf(request->len()); - response->set_ret( - recv(request->sockfd(), buf.data(), buf.size(), request->flags())); - response->set_errno_(errno); - response->set_buf(buf.data(), response->ret()); - return ::grpc::Status::OK; - } -}; - -// Parse command line options. Returns a pointer to the first argument beyond -// the options. -void parse_command_line_options(int argc, char *argv[], std::string *ip, - int *port) { - static struct option options[] = {{"ip", required_argument, NULL, 1}, - {"port", required_argument, NULL, 2}, - {0, 0, 0, 0}}; - - // Parse the arguments. - int c; - while ((c = getopt_long(argc, argv, "", options, NULL)) > 0) { - if (c == 1) { - *ip = optarg; - } else if (c == 2) { - *port = std::stoi(std::string(optarg)); - } - } -} - -void run_server(const std::string &ip, int port) { - PosixImpl posix_service; - grpc::ServerBuilder builder; - std::string server_address = ip + ":" + std::to_string(port); - // Set the authentication mechanism. - std::shared_ptr<grpc::ServerCredentials> creds = - grpc::InsecureServerCredentials(); - builder.AddListeningPort(server_address, creds); - builder.RegisterService(&posix_service); - - std::unique_ptr<grpc::Server> server(builder.BuildAndStart()); - std::cerr << "Server listening on " << server_address << std::endl; - server->Wait(); - std::cerr << "posix_server is finished." << std::endl; -} - -int main(int argc, char *argv[]) { - std::cerr << "posix_server is starting." << std::endl; - std::string ip; - int port; - parse_command_line_options(argc, argv, &ip, &port); - - std::cerr << "Got IP " << ip << " and port " << port << "." << std::endl; - run_server(ip, port); -} diff --git a/test/packetimpact/proto/BUILD b/test/packetimpact/proto/BUILD deleted file mode 100644 index 4a4370f42..000000000 --- a/test/packetimpact/proto/BUILD +++ /dev/null @@ -1,12 +0,0 @@ -load("//tools:defs.bzl", "proto_library") - -package( - default_visibility = ["//test/packetimpact:__subpackages__"], - licenses = ["notice"], -) - -proto_library( - name = "posix_server", - srcs = ["posix_server.proto"], - has_services = 1, -) diff --git a/test/packetimpact/proto/posix_server.proto b/test/packetimpact/proto/posix_server.proto deleted file mode 100644 index 4035e1ee6..000000000 --- a/test/packetimpact/proto/posix_server.proto +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -syntax = "proto3"; - -package posix_server; - -message SockaddrIn { - int32 family = 1; - uint32 port = 2; - bytes addr = 3; -} - -message SockaddrIn6 { - uint32 family = 1; - uint32 port = 2; - uint32 flowinfo = 3; - bytes addr = 4; - uint32 scope_id = 5; -} - -message Sockaddr { - oneof sockaddr { - SockaddrIn in = 1; - SockaddrIn6 in6 = 2; - } -} - -message Timeval { - int64 seconds = 1; - int64 microseconds = 2; -} - -// Request and Response pairs for each Posix service RPC call, sorted. - -message AcceptRequest { - int32 sockfd = 1; -} - -message AcceptResponse { - int32 fd = 1; - int32 errno_ = 2; // "errno" may fail to compile in c++. - Sockaddr addr = 3; -} - -message BindRequest { - int32 sockfd = 1; - Sockaddr addr = 2; -} - -message BindResponse { - int32 ret = 1; - int32 errno_ = 2; // "errno" may fail to compile in c++. -} - -message CloseRequest { - int32 fd = 1; -} - -message CloseResponse { - int32 ret = 1; - int32 errno_ = 2; // "errno" may fail to compile in c++. -} - -message GetSockNameRequest { - int32 sockfd = 1; -} - -message GetSockNameResponse { - int32 ret = 1; - int32 errno_ = 2; // "errno" may fail to compile in c++. - Sockaddr addr = 3; -} - -message ListenRequest { - int32 sockfd = 1; - int32 backlog = 2; -} - -message ListenResponse { - int32 ret = 1; - int32 errno_ = 2; // "errno" may fail to compile in c++. -} - -message SendRequest { - int32 sockfd = 1; - bytes buf = 2; - int32 flags = 3; -} - -message SendResponse { - int32 ret = 1; - int32 errno_ = 2; -} - -message SetSockOptRequest { - int32 sockfd = 1; - int32 level = 2; - int32 optname = 3; - bytes optval = 4; -} - -message SetSockOptResponse { - int32 ret = 1; - int32 errno_ = 2; // "errno" may fail to compile in c++. -} - -message SetSockOptIntRequest { - int32 sockfd = 1; - int32 level = 2; - int32 optname = 3; - int32 intval = 4; -} - -message SetSockOptIntResponse { - int32 ret = 1; - int32 errno_ = 2; -} - -message SetSockOptTimevalRequest { - int32 sockfd = 1; - int32 level = 2; - int32 optname = 3; - Timeval timeval = 4; -} - -message SetSockOptTimevalResponse { - int32 ret = 1; - int32 errno_ = 2; // "errno" may fail to compile in c++. -} - -message SocketRequest { - int32 domain = 1; - int32 type = 2; - int32 protocol = 3; -} - -message SocketResponse { - int32 fd = 1; - int32 errno_ = 2; // "errno" may fail to compile in c++. -} - -message RecvRequest { - int32 sockfd = 1; - int32 len = 2; - int32 flags = 3; -} - -message RecvResponse { - int32 ret = 1; - int32 errno_ = 2; // "errno" may fail to compile in c++. - bytes buf = 3; -} - -service Posix { - // Call accept() on the DUT. - rpc Accept(AcceptRequest) returns (AcceptResponse); - // Call bind() on the DUT. - rpc Bind(BindRequest) returns (BindResponse); - // Call close() on the DUT. - rpc Close(CloseRequest) returns (CloseResponse); - // Call getsockname() on the DUT. - rpc GetSockName(GetSockNameRequest) returns (GetSockNameResponse); - // Call listen() on the DUT. - rpc Listen(ListenRequest) returns (ListenResponse); - // Call send() on the DUT. - rpc Send(SendRequest) returns (SendResponse); - // Call setsockopt() on the DUT. You should prefer one of the other - // SetSockOpt* functions with a more structured optval or else you may get the - // encoding wrong, such as making a bad assumption about the server's word - // sizes or endianness. - rpc SetSockOpt(SetSockOptRequest) returns (SetSockOptResponse); - // Call setsockopt() on the DUT with an int optval. - rpc SetSockOptInt(SetSockOptIntRequest) returns (SetSockOptIntResponse); - // Call setsockopt() on the DUT with a Timeval optval. - rpc SetSockOptTimeval(SetSockOptTimevalRequest) - returns (SetSockOptTimevalResponse); - // Call socket() on the DUT. - rpc Socket(SocketRequest) returns (SocketResponse); - // Call recv() on the DUT. - rpc Recv(RecvRequest) returns (RecvResponse); -} diff --git a/test/packetimpact/testbench/BUILD b/test/packetimpact/testbench/BUILD deleted file mode 100644 index 838a10ffe..000000000 --- a/test/packetimpact/testbench/BUILD +++ /dev/null @@ -1,40 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package( - default_visibility = ["//test/packetimpact:__subpackages__"], - licenses = ["notice"], -) - -go_library( - name = "testbench", - srcs = [ - "connections.go", - "dut.go", - "dut_client.go", - "layers.go", - "rawsockets.go", - ], - deps = [ - "//pkg/tcpip", - "//pkg/tcpip/buffer", - "//pkg/tcpip/header", - "//pkg/tcpip/seqnum", - "//pkg/usermem", - "//test/packetimpact/proto:posix_server_go_proto", - "@com_github_google_go-cmp//cmp:go_default_library", - "@com_github_google_go-cmp//cmp/cmpopts:go_default_library", - "@com_github_imdario_mergo//: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", - ], -) - -go_test( - name = "testbench_test", - size = "small", - srcs = ["layers_test.go"], - library = ":testbench", - deps = ["//pkg/tcpip"], -) diff --git a/test/packetimpact/testbench/connections.go b/test/packetimpact/testbench/connections.go deleted file mode 100644 index 2b8e2f005..000000000 --- a/test/packetimpact/testbench/connections.go +++ /dev/null @@ -1,456 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package testbench has utilities to send and receive packets and also command -// the DUT to run POSIX functions. -package testbench - -import ( - "flag" - "fmt" - "math/rand" - "net" - "strings" - "testing" - "time" - - "github.com/mohae/deepcopy" - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/pkg/tcpip/seqnum" -) - -var localIPv4 = flag.String("local_ipv4", "", "local IPv4 address for test packets") -var remoteIPv4 = flag.String("remote_ipv4", "", "remote IPv4 address for test packets") -var localMAC = flag.String("local_mac", "", "local mac address for test packets") -var remoteMAC = flag.String("remote_mac", "", "remote mac address for test packets") - -// pickPort makes a new socket and returns the socket FD and port. The caller -// must close the FD when done with the port if there is no error. -func pickPort() (int, uint16, error) { - fd, err := unix.Socket(unix.AF_INET, unix.SOCK_STREAM, 0) - if err != nil { - return -1, 0, err - } - var sa unix.SockaddrInet4 - copy(sa.Addr[0:4], net.ParseIP(*localIPv4).To4()) - if err := unix.Bind(fd, &sa); err != nil { - unix.Close(fd) - return -1, 0, err - } - newSockAddr, err := unix.Getsockname(fd) - if err != nil { - unix.Close(fd) - return -1, 0, err - } - newSockAddrInet4, ok := newSockAddr.(*unix.SockaddrInet4) - if !ok { - unix.Close(fd) - return -1, 0, fmt.Errorf("can't cast Getsockname result to SockaddrInet4") - } - return fd, uint16(newSockAddrInet4.Port), nil -} - -// TCPIPv4 maintains state about a TCP/IPv4 connection. -type TCPIPv4 struct { - outgoing Layers - incoming Layers - LocalSeqNum seqnum.Value - RemoteSeqNum seqnum.Value - SynAck *TCP - sniffer Sniffer - injector Injector - portPickerFD int - t *testing.T -} - -// tcpLayerIndex is the position of the TCP layer in the TCPIPv4 connection. It -// is the third, after Ethernet and IPv4. -const tcpLayerIndex int = 2 - -// NewTCPIPv4 creates a new TCPIPv4 connection with reasonable defaults. -func NewTCPIPv4(t *testing.T, outgoingTCP, incomingTCP TCP) TCPIPv4 { - lMAC, err := tcpip.ParseMACAddress(*localMAC) - if err != nil { - t.Fatalf("can't parse localMAC %q: %s", *localMAC, err) - } - - rMAC, err := tcpip.ParseMACAddress(*remoteMAC) - if err != nil { - t.Fatalf("can't parse remoteMAC %q: %s", *remoteMAC, err) - } - - portPickerFD, localPort, err := pickPort() - if err != nil { - t.Fatalf("can't pick a port: %s", err) - } - lIP := tcpip.Address(net.ParseIP(*localIPv4).To4()) - rIP := tcpip.Address(net.ParseIP(*remoteIPv4).To4()) - - sniffer, err := NewSniffer(t) - if err != nil { - t.Fatalf("can't make new sniffer: %s", err) - } - - injector, err := NewInjector(t) - if err != nil { - t.Fatalf("can't make new injector: %s", err) - } - - newOutgoingTCP := &TCP{ - SrcPort: &localPort, - } - if err := newOutgoingTCP.merge(outgoingTCP); err != nil { - t.Fatalf("can't merge %+v into %+v: %s", outgoingTCP, newOutgoingTCP, err) - } - newIncomingTCP := &TCP{ - DstPort: &localPort, - } - if err := newIncomingTCP.merge(incomingTCP); err != nil { - t.Fatalf("can't merge %+v into %+v: %s", incomingTCP, newIncomingTCP, err) - } - return TCPIPv4{ - outgoing: Layers{ - &Ether{SrcAddr: &lMAC, DstAddr: &rMAC}, - &IPv4{SrcAddr: &lIP, DstAddr: &rIP}, - newOutgoingTCP}, - incoming: Layers{ - &Ether{SrcAddr: &rMAC, DstAddr: &lMAC}, - &IPv4{SrcAddr: &rIP, DstAddr: &lIP}, - newIncomingTCP}, - sniffer: sniffer, - injector: injector, - portPickerFD: portPickerFD, - t: t, - LocalSeqNum: seqnum.Value(rand.Uint32()), - } -} - -// Close the injector and sniffer associated with this connection. -func (conn *TCPIPv4) Close() { - conn.sniffer.Close() - conn.injector.Close() - if err := unix.Close(conn.portPickerFD); err != nil { - conn.t.Fatalf("can't close portPickerFD: %s", err) - } - conn.portPickerFD = -1 -} - -// CreateFrame builds a frame for the connection with tcp overriding defaults -// and additionalLayers added after the TCP header. -func (conn *TCPIPv4) CreateFrame(tcp TCP, additionalLayers ...Layer) Layers { - if tcp.SeqNum == nil { - tcp.SeqNum = Uint32(uint32(conn.LocalSeqNum)) - } - if tcp.AckNum == nil { - tcp.AckNum = Uint32(uint32(conn.RemoteSeqNum)) - } - layersToSend := deepcopy.Copy(conn.outgoing).(Layers) - if err := layersToSend[tcpLayerIndex].(*TCP).merge(tcp); err != nil { - conn.t.Fatalf("can't merge %+v into %+v: %s", tcp, layersToSend[tcpLayerIndex], err) - } - layersToSend = append(layersToSend, additionalLayers...) - return layersToSend -} - -// SendFrame sends a frame with reasonable defaults. -func (conn *TCPIPv4) SendFrame(frame Layers) { - outBytes, err := frame.toBytes() - if err != nil { - conn.t.Fatalf("can't build outgoing TCP packet: %s", err) - } - conn.injector.Send(outBytes) - - // Compute the next TCP sequence number. - for i := tcpLayerIndex + 1; i < len(frame); i++ { - conn.LocalSeqNum.UpdateForward(seqnum.Size(frame[i].length())) - } - tcp := frame[tcpLayerIndex].(*TCP) - if tcp.Flags != nil && *tcp.Flags&(header.TCPFlagSyn|header.TCPFlagFin) != 0 { - conn.LocalSeqNum.UpdateForward(1) - } -} - -// Send a packet with reasonable defaults and override some fields by tcp. -func (conn *TCPIPv4) Send(tcp TCP, additionalLayers ...Layer) { - conn.SendFrame(conn.CreateFrame(tcp, additionalLayers...)) -} - -// Recv gets a packet from the sniffer within the timeout provided. -// If no packet arrives before the timeout, it returns nil. -func (conn *TCPIPv4) Recv(timeout time.Duration) *TCP { - layers := conn.RecvFrame(timeout) - if tcpLayerIndex < len(layers) { - return layers[tcpLayerIndex].(*TCP) - } - return nil -} - -// RecvFrame gets a frame (of type Layers) within the timeout provided. -// If no frame arrives before the timeout, it returns nil. -func (conn *TCPIPv4) RecvFrame(timeout time.Duration) Layers { - deadline := time.Now().Add(timeout) - for { - timeout = time.Until(deadline) - if timeout <= 0 { - break - } - b := conn.sniffer.Recv(timeout) - if b == nil { - break - } - layers := Parse(ParseEther, b) - if !conn.incoming.match(layers) { - continue // Ignore packets that don't match the expected incoming. - } - tcpHeader := (layers[tcpLayerIndex]).(*TCP) - conn.RemoteSeqNum = seqnum.Value(*tcpHeader.SeqNum) - if *tcpHeader.Flags&(header.TCPFlagSyn|header.TCPFlagFin) != 0 { - conn.RemoteSeqNum.UpdateForward(1) - } - for i := tcpLayerIndex + 1; i < len(layers); i++ { - conn.RemoteSeqNum.UpdateForward(seqnum.Size(layers[i].length())) - } - return layers - } - return nil -} - -// Drain drains the sniffer's receive buffer by receiving packets until there's -// nothing else to receive. -func (conn *TCPIPv4) Drain() { - conn.sniffer.Drain() -} - -// Expect a packet that matches the provided tcp within the timeout specified. -// If it doesn't arrive in time, it returns nil. -func (conn *TCPIPv4) Expect(tcp TCP, timeout time.Duration) (*TCP, error) { - // We cannot implement this directly using ExpectFrame as we cannot specify - // the Payload part. - deadline := time.Now().Add(timeout) - var allTCP []string - for { - var gotTCP *TCP - if timeout = time.Until(deadline); timeout > 0 { - gotTCP = conn.Recv(timeout) - } - if gotTCP == nil { - return nil, fmt.Errorf("got %d packets:\n%s", len(allTCP), strings.Join(allTCP, "\n")) - } - if tcp.match(gotTCP) { - return gotTCP, nil - } - allTCP = append(allTCP, gotTCP.String()) - } -} - -// ExpectFrame expects a frame that matches the specified layers within the -// timeout specified. If it doesn't arrive in time, it returns nil. -func (conn *TCPIPv4) ExpectFrame(layers Layers, timeout time.Duration) Layers { - deadline := time.Now().Add(timeout) - for { - timeout = time.Until(deadline) - if timeout <= 0 { - return nil - } - gotLayers := conn.RecvFrame(timeout) - if layers.match(gotLayers) { - return gotLayers - } - } -} - -// ExpectData is a convenient method that expects a TCP packet along with -// the payload to arrive within the timeout specified. If it doesn't arrive -// in time, it causes a fatal test failure. -func (conn *TCPIPv4) ExpectData(tcp TCP, data []byte, timeout time.Duration) { - expected := []Layer{&Ether{}, &IPv4{}, &tcp} - if len(data) > 0 { - expected = append(expected, &Payload{Bytes: data}) - } - if conn.ExpectFrame(expected, timeout) == nil { - conn.t.Fatalf("expected to get a TCP frame %s with payload %x", &tcp, data) - } -} - -// Handshake performs a TCP 3-way handshake. -func (conn *TCPIPv4) Handshake() { - // Send the SYN. - conn.Send(TCP{Flags: Uint8(header.TCPFlagSyn)}) - - // Wait for the SYN-ACK. - synAck, err := conn.Expect(TCP{Flags: Uint8(header.TCPFlagSyn | header.TCPFlagAck)}, time.Second) - if synAck == nil { - conn.t.Fatalf("didn't get synack during handshake: %s", err) - } - conn.SynAck = synAck - - // Send an ACK. - conn.Send(TCP{Flags: Uint8(header.TCPFlagAck)}) -} - -// UDPIPv4 maintains state about a UDP/IPv4 connection. -type UDPIPv4 struct { - outgoing Layers - incoming Layers - sniffer Sniffer - injector Injector - portPickerFD int - t *testing.T -} - -// udpLayerIndex is the position of the UDP layer in the UDPIPv4 connection. It -// is the third, after Ethernet and IPv4. -const udpLayerIndex int = 2 - -// NewUDPIPv4 creates a new UDPIPv4 connection with reasonable defaults. -func NewUDPIPv4(t *testing.T, outgoingUDP, incomingUDP UDP) UDPIPv4 { - lMAC, err := tcpip.ParseMACAddress(*localMAC) - if err != nil { - t.Fatalf("can't parse localMAC %q: %s", *localMAC, err) - } - - rMAC, err := tcpip.ParseMACAddress(*remoteMAC) - if err != nil { - t.Fatalf("can't parse remoteMAC %q: %s", *remoteMAC, err) - } - - portPickerFD, localPort, err := pickPort() - if err != nil { - t.Fatalf("can't pick a port: %s", err) - } - lIP := tcpip.Address(net.ParseIP(*localIPv4).To4()) - rIP := tcpip.Address(net.ParseIP(*remoteIPv4).To4()) - - sniffer, err := NewSniffer(t) - if err != nil { - t.Fatalf("can't make new sniffer: %s", err) - } - - injector, err := NewInjector(t) - if err != nil { - t.Fatalf("can't make new injector: %s", err) - } - - newOutgoingUDP := &UDP{ - SrcPort: &localPort, - } - if err := newOutgoingUDP.merge(outgoingUDP); err != nil { - t.Fatalf("can't merge %+v into %+v: %s", outgoingUDP, newOutgoingUDP, err) - } - newIncomingUDP := &UDP{ - DstPort: &localPort, - } - if err := newIncomingUDP.merge(incomingUDP); err != nil { - t.Fatalf("can't merge %+v into %+v: %s", incomingUDP, newIncomingUDP, err) - } - return UDPIPv4{ - outgoing: Layers{ - &Ether{SrcAddr: &lMAC, DstAddr: &rMAC}, - &IPv4{SrcAddr: &lIP, DstAddr: &rIP}, - newOutgoingUDP}, - incoming: Layers{ - &Ether{SrcAddr: &rMAC, DstAddr: &lMAC}, - &IPv4{SrcAddr: &rIP, DstAddr: &lIP}, - newIncomingUDP}, - sniffer: sniffer, - injector: injector, - portPickerFD: portPickerFD, - t: t, - } -} - -// Close the injector and sniffer associated with this connection. -func (conn *UDPIPv4) Close() { - conn.sniffer.Close() - conn.injector.Close() - if err := unix.Close(conn.portPickerFD); err != nil { - conn.t.Fatalf("can't close portPickerFD: %s", err) - } - conn.portPickerFD = -1 -} - -// CreateFrame builds a frame for the connection with the provided udp -// overriding defaults and the additionalLayers added after the UDP header. -func (conn *UDPIPv4) CreateFrame(udp UDP, additionalLayers ...Layer) Layers { - layersToSend := deepcopy.Copy(conn.outgoing).(Layers) - if err := layersToSend[udpLayerIndex].(*UDP).merge(udp); err != nil { - conn.t.Fatalf("can't merge %+v into %+v: %s", udp, layersToSend[udpLayerIndex], err) - } - layersToSend = append(layersToSend, additionalLayers...) - return layersToSend -} - -// SendFrame sends a frame with reasonable defaults. -func (conn *UDPIPv4) SendFrame(frame Layers) { - outBytes, err := frame.toBytes() - if err != nil { - conn.t.Fatalf("can't build outgoing UDP packet: %s", err) - } - conn.injector.Send(outBytes) -} - -// Send a packet with reasonable defaults and override some fields by udp. -func (conn *UDPIPv4) Send(udp UDP, additionalLayers ...Layer) { - conn.SendFrame(conn.CreateFrame(udp, additionalLayers...)) -} - -// Recv gets a packet from the sniffer within the timeout provided. If no packet -// arrives before the timeout, it returns nil. -func (conn *UDPIPv4) Recv(timeout time.Duration) *UDP { - deadline := time.Now().Add(timeout) - for { - timeout = time.Until(deadline) - if timeout <= 0 { - break - } - b := conn.sniffer.Recv(timeout) - if b == nil { - break - } - layers := Parse(ParseEther, b) - if !conn.incoming.match(layers) { - continue // Ignore packets that don't match the expected incoming. - } - return (layers[udpLayerIndex]).(*UDP) - } - return nil -} - -// Drain drains the sniffer's receive buffer by receiving packets until there's -// nothing else to receive. -func (conn *UDPIPv4) Drain() { - conn.sniffer.Drain() -} - -// Expect a packet that matches the provided udp within the timeout specified. -// If it doesn't arrive in time, the test fails. -func (conn *UDPIPv4) Expect(udp UDP, timeout time.Duration) (*UDP, error) { - deadline := time.Now().Add(timeout) - var allUDP []string - for { - var gotUDP *UDP - if timeout = time.Until(deadline); timeout > 0 { - gotUDP = conn.Recv(timeout) - } - if gotUDP == nil { - return nil, fmt.Errorf("got %d packets:\n%s", len(allUDP), strings.Join(allUDP, "\n")) - } - if udp.match(gotUDP) { - return gotUDP, nil - } - allUDP = append(allUDP, gotUDP.String()) - } -} diff --git a/test/packetimpact/testbench/dut.go b/test/packetimpact/testbench/dut.go deleted file mode 100644 index 9335909c0..000000000 --- a/test/packetimpact/testbench/dut.go +++ /dev/null @@ -1,473 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package testbench - -import ( - "context" - "flag" - "net" - "strconv" - "syscall" - "testing" - "time" - - pb "gvisor.dev/gvisor/test/packetimpact/proto/posix_server_go_proto" - - "golang.org/x/sys/unix" - "google.golang.org/grpc" - "google.golang.org/grpc/keepalive" -) - -var ( - posixServerIP = flag.String("posix_server_ip", "", "ip address to listen to for UDP commands") - posixServerPort = flag.Int("posix_server_port", 40000, "port to listen to for UDP commands") - rpcTimeout = flag.Duration("rpc_timeout", 100*time.Millisecond, "gRPC timeout") - rpcKeepalive = flag.Duration("rpc_keepalive", 10*time.Second, "gRPC keepalive") -) - -// DUT communicates with the DUT to force it to make POSIX calls. -type DUT struct { - t *testing.T - conn *grpc.ClientConn - posixServer PosixClient -} - -// NewDUT creates a new connection with the DUT over gRPC. -func NewDUT(t *testing.T) DUT { - flag.Parse() - posixServerAddress := *posixServerIP + ":" + strconv.Itoa(*posixServerPort) - conn, err := grpc.Dial(posixServerAddress, grpc.WithInsecure(), grpc.WithKeepaliveParams(keepalive.ClientParameters{Timeout: *rpcKeepalive})) - if err != nil { - t.Fatalf("failed to grpc.Dial(%s): %s", posixServerAddress, err) - } - posixServer := NewPosixClient(conn) - return DUT{ - t: t, - conn: conn, - posixServer: posixServer, - } -} - -// TearDown closes the underlying connection. -func (dut *DUT) TearDown() { - dut.conn.Close() -} - -func (dut *DUT) sockaddrToProto(sa unix.Sockaddr) *pb.Sockaddr { - dut.t.Helper() - switch s := sa.(type) { - case *unix.SockaddrInet4: - return &pb.Sockaddr{ - Sockaddr: &pb.Sockaddr_In{ - In: &pb.SockaddrIn{ - Family: unix.AF_INET, - Port: uint32(s.Port), - Addr: s.Addr[:], - }, - }, - } - case *unix.SockaddrInet6: - return &pb.Sockaddr{ - Sockaddr: &pb.Sockaddr_In6{ - In6: &pb.SockaddrIn6{ - Family: unix.AF_INET6, - Port: uint32(s.Port), - Flowinfo: 0, - ScopeId: s.ZoneId, - Addr: s.Addr[:], - }, - }, - } - } - dut.t.Fatalf("can't parse Sockaddr: %+v", sa) - return nil -} - -func (dut *DUT) protoToSockaddr(sa *pb.Sockaddr) unix.Sockaddr { - dut.t.Helper() - switch s := sa.Sockaddr.(type) { - case *pb.Sockaddr_In: - ret := unix.SockaddrInet4{ - Port: int(s.In.GetPort()), - } - copy(ret.Addr[:], s.In.GetAddr()) - return &ret - case *pb.Sockaddr_In6: - ret := unix.SockaddrInet6{ - Port: int(s.In6.GetPort()), - ZoneId: s.In6.GetScopeId(), - } - copy(ret.Addr[:], s.In6.GetAddr()) - } - dut.t.Fatalf("can't parse Sockaddr: %+v", sa) - return nil -} - -// CreateBoundSocket makes a new socket on the DUT, with type typ and protocol -// proto, and bound to the IP address addr. Returns the new file descriptor and -// the port that was selected on the DUT. -func (dut *DUT) CreateBoundSocket(typ, proto int32, addr net.IP) (int32, uint16) { - dut.t.Helper() - var fd int32 - if addr.To4() != nil { - fd = dut.Socket(unix.AF_INET, typ, proto) - sa := unix.SockaddrInet4{} - copy(sa.Addr[:], addr.To4()) - dut.Bind(fd, &sa) - } else if addr.To16() != nil { - fd = dut.Socket(unix.AF_INET6, typ, proto) - sa := unix.SockaddrInet6{} - copy(sa.Addr[:], addr.To16()) - dut.Bind(fd, &sa) - } else { - dut.t.Fatal("unknown ip addr type for remoteIP") - } - sa := dut.GetSockName(fd) - var port int - switch s := sa.(type) { - case *unix.SockaddrInet4: - port = s.Port - case *unix.SockaddrInet6: - port = s.Port - default: - dut.t.Fatalf("unknown sockaddr type from getsockname: %t", sa) - } - return fd, uint16(port) -} - -// CreateListener makes a new TCP connection. If it fails, the test ends. -func (dut *DUT) CreateListener(typ, proto, backlog int32) (int32, uint16) { - fd, remotePort := dut.CreateBoundSocket(typ, proto, net.ParseIP(*remoteIPv4)) - dut.Listen(fd, backlog) - return fd, remotePort -} - -// All the functions that make gRPC calls to the Posix service are below, sorted -// alphabetically. - -// Accept calls accept on the DUT and causes a fatal test failure if it doesn't -// succeed. If more control over the timeout or error handling is needed, use -// AcceptWithErrno. -func (dut *DUT) Accept(sockfd int32) (int32, unix.Sockaddr) { - dut.t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout) - defer cancel() - fd, sa, err := dut.AcceptWithErrno(ctx, sockfd) - if fd < 0 { - dut.t.Fatalf("failed to accept: %s", err) - } - return fd, sa -} - -// AcceptWithErrno calls accept on the DUT. -func (dut *DUT) AcceptWithErrno(ctx context.Context, sockfd int32) (int32, unix.Sockaddr, error) { - dut.t.Helper() - req := pb.AcceptRequest{ - Sockfd: sockfd, - } - resp, err := dut.posixServer.Accept(ctx, &req) - if err != nil { - dut.t.Fatalf("failed to call Accept: %s", err) - } - return resp.GetFd(), dut.protoToSockaddr(resp.GetAddr()), syscall.Errno(resp.GetErrno_()) -} - -// Bind calls bind on the DUT and causes a fatal test failure if it doesn't -// succeed. If more control over the timeout or error handling is -// needed, use BindWithErrno. -func (dut *DUT) Bind(fd int32, sa unix.Sockaddr) { - dut.t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout) - defer cancel() - ret, err := dut.BindWithErrno(ctx, fd, sa) - if ret != 0 { - dut.t.Fatalf("failed to bind socket: %s", err) - } -} - -// BindWithErrno calls bind on the DUT. -func (dut *DUT) BindWithErrno(ctx context.Context, fd int32, sa unix.Sockaddr) (int32, error) { - dut.t.Helper() - req := pb.BindRequest{ - Sockfd: fd, - Addr: dut.sockaddrToProto(sa), - } - resp, err := dut.posixServer.Bind(ctx, &req) - if err != nil { - dut.t.Fatalf("failed to call Bind: %s", err) - } - return resp.GetRet(), syscall.Errno(resp.GetErrno_()) -} - -// Close calls close on the DUT and causes a fatal test failure if it doesn't -// succeed. If more control over the timeout or error handling is needed, use -// CloseWithErrno. -func (dut *DUT) Close(fd int32) { - dut.t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout) - defer cancel() - ret, err := dut.CloseWithErrno(ctx, fd) - if ret != 0 { - dut.t.Fatalf("failed to close: %s", err) - } -} - -// CloseWithErrno calls close on the DUT. -func (dut *DUT) CloseWithErrno(ctx context.Context, fd int32) (int32, error) { - dut.t.Helper() - req := pb.CloseRequest{ - Fd: fd, - } - resp, err := dut.posixServer.Close(ctx, &req) - if err != nil { - dut.t.Fatalf("failed to call Close: %s", err) - } - return resp.GetRet(), syscall.Errno(resp.GetErrno_()) -} - -// GetSockName calls getsockname on the DUT and causes a fatal test failure if -// it doesn't succeed. If more control over the timeout or error handling is -// needed, use GetSockNameWithErrno. -func (dut *DUT) GetSockName(sockfd int32) unix.Sockaddr { - dut.t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout) - defer cancel() - ret, sa, err := dut.GetSockNameWithErrno(ctx, sockfd) - if ret != 0 { - dut.t.Fatalf("failed to getsockname: %s", err) - } - return sa -} - -// GetSockNameWithErrno calls getsockname on the DUT. -func (dut *DUT) GetSockNameWithErrno(ctx context.Context, sockfd int32) (int32, unix.Sockaddr, error) { - dut.t.Helper() - req := pb.GetSockNameRequest{ - Sockfd: sockfd, - } - resp, err := dut.posixServer.GetSockName(ctx, &req) - if err != nil { - dut.t.Fatalf("failed to call Bind: %s", err) - } - return resp.GetRet(), dut.protoToSockaddr(resp.GetAddr()), syscall.Errno(resp.GetErrno_()) -} - -// Listen calls listen on the DUT and causes a fatal test failure if it doesn't -// succeed. If more control over the timeout or error handling is needed, use -// ListenWithErrno. -func (dut *DUT) Listen(sockfd, backlog int32) { - dut.t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout) - defer cancel() - ret, err := dut.ListenWithErrno(ctx, sockfd, backlog) - if ret != 0 { - dut.t.Fatalf("failed to listen: %s", err) - } -} - -// ListenWithErrno calls listen on the DUT. -func (dut *DUT) ListenWithErrno(ctx context.Context, sockfd, backlog int32) (int32, error) { - dut.t.Helper() - req := pb.ListenRequest{ - Sockfd: sockfd, - Backlog: backlog, - } - resp, err := dut.posixServer.Listen(ctx, &req) - if err != nil { - dut.t.Fatalf("failed to call Listen: %s", err) - } - return resp.GetRet(), syscall.Errno(resp.GetErrno_()) -} - -// Send calls send on the DUT and causes a fatal test failure if it doesn't -// succeed. If more control over the timeout or error handling is needed, use -// SendWithErrno. -func (dut *DUT) Send(sockfd int32, buf []byte, flags int32) int32 { - dut.t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout) - defer cancel() - ret, err := dut.SendWithErrno(ctx, sockfd, buf, flags) - if ret == -1 { - dut.t.Fatalf("failed to send: %s", err) - } - return ret -} - -// SendWithErrno calls send on the DUT. -func (dut *DUT) SendWithErrno(ctx context.Context, sockfd int32, buf []byte, flags int32) (int32, error) { - dut.t.Helper() - req := pb.SendRequest{ - Sockfd: sockfd, - Buf: buf, - Flags: flags, - } - resp, err := dut.posixServer.Send(ctx, &req) - if err != nil { - dut.t.Fatalf("failed to call Send: %s", err) - } - return resp.GetRet(), syscall.Errno(resp.GetErrno_()) -} - -// SetSockOpt calls setsockopt on the DUT and causes a fatal test failure if it -// doesn't succeed. If more control over the timeout or error handling is -// needed, use SetSockOptWithErrno. Because endianess and the width of values -// might differ between the testbench and DUT architectures, prefer to use a -// more specific SetSockOptXxx function. -func (dut *DUT) SetSockOpt(sockfd, level, optname int32, optval []byte) { - dut.t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout) - defer cancel() - ret, err := dut.SetSockOptWithErrno(ctx, sockfd, level, optname, optval) - if ret != 0 { - dut.t.Fatalf("failed to SetSockOpt: %s", err) - } -} - -// SetSockOptWithErrno calls setsockopt on the DUT. Because endianess and the -// width of values might differ between the testbench and DUT architectures, -// prefer to use a more specific SetSockOptXxxWithErrno function. -func (dut *DUT) SetSockOptWithErrno(ctx context.Context, sockfd, level, optname int32, optval []byte) (int32, error) { - dut.t.Helper() - req := pb.SetSockOptRequest{ - Sockfd: sockfd, - Level: level, - Optname: optname, - Optval: optval, - } - resp, err := dut.posixServer.SetSockOpt(ctx, &req) - if err != nil { - dut.t.Fatalf("failed to call SetSockOpt: %s", err) - } - return resp.GetRet(), syscall.Errno(resp.GetErrno_()) -} - -// SetSockOptInt calls setsockopt on the DUT and causes a fatal test failure -// if it doesn't succeed. If more control over the int optval or error handling -// is needed, use SetSockOptIntWithErrno. -func (dut *DUT) SetSockOptInt(sockfd, level, optname, optval int32) { - dut.t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout) - defer cancel() - ret, err := dut.SetSockOptIntWithErrno(ctx, sockfd, level, optname, optval) - if ret != 0 { - dut.t.Fatalf("failed to SetSockOptInt: %s", err) - } -} - -// SetSockOptIntWithErrno calls setsockopt with an integer optval. -func (dut *DUT) SetSockOptIntWithErrno(ctx context.Context, sockfd, level, optname, optval int32) (int32, error) { - dut.t.Helper() - req := pb.SetSockOptIntRequest{ - Sockfd: sockfd, - Level: level, - Optname: optname, - Intval: optval, - } - resp, err := dut.posixServer.SetSockOptInt(ctx, &req) - if err != nil { - dut.t.Fatalf("failed to call SetSockOptInt: %s", err) - } - return resp.GetRet(), syscall.Errno(resp.GetErrno_()) -} - -// SetSockOptTimeval calls setsockopt on the DUT and causes a fatal test failure -// if it doesn't succeed. If more control over the timeout or error handling is -// needed, use SetSockOptTimevalWithErrno. -func (dut *DUT) SetSockOptTimeval(sockfd, level, optname int32, tv *unix.Timeval) { - dut.t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout) - defer cancel() - ret, err := dut.SetSockOptTimevalWithErrno(ctx, sockfd, level, optname, tv) - if ret != 0 { - dut.t.Fatalf("failed to SetSockOptTimeval: %s", err) - } -} - -// SetSockOptTimevalWithErrno calls setsockopt with the timeval converted to -// bytes. -func (dut *DUT) SetSockOptTimevalWithErrno(ctx context.Context, sockfd, level, optname int32, tv *unix.Timeval) (int32, error) { - dut.t.Helper() - timeval := pb.Timeval{ - Seconds: int64(tv.Sec), - Microseconds: int64(tv.Usec), - } - req := pb.SetSockOptTimevalRequest{ - Sockfd: sockfd, - Level: level, - Optname: optname, - Timeval: &timeval, - } - resp, err := dut.posixServer.SetSockOptTimeval(ctx, &req) - if err != nil { - dut.t.Fatalf("failed to call SetSockOptTimeval: %s", err) - } - return resp.GetRet(), syscall.Errno(resp.GetErrno_()) -} - -// Socket calls socket on the DUT and returns the file descriptor. If socket -// fails on the DUT, the test ends. -func (dut *DUT) Socket(domain, typ, proto int32) int32 { - dut.t.Helper() - fd, err := dut.SocketWithErrno(domain, typ, proto) - if fd < 0 { - dut.t.Fatalf("failed to create socket: %s", err) - } - return fd -} - -// SocketWithErrno calls socket on the DUT and returns the fd and errno. -func (dut *DUT) SocketWithErrno(domain, typ, proto int32) (int32, error) { - dut.t.Helper() - req := pb.SocketRequest{ - Domain: domain, - Type: typ, - Protocol: proto, - } - ctx := context.Background() - resp, err := dut.posixServer.Socket(ctx, &req) - if err != nil { - dut.t.Fatalf("failed to call Socket: %s", err) - } - return resp.GetFd(), syscall.Errno(resp.GetErrno_()) -} - -// Recv calls recv on the DUT and causes a fatal test failure if it doesn't -// succeed. If more control over the timeout or error handling is needed, use -// RecvWithErrno. -func (dut *DUT) Recv(sockfd, len, flags int32) []byte { - dut.t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout) - defer cancel() - ret, buf, err := dut.RecvWithErrno(ctx, sockfd, len, flags) - if ret == -1 { - dut.t.Fatalf("failed to recv: %s", err) - } - return buf -} - -// RecvWithErrno calls recv on the DUT. -func (dut *DUT) RecvWithErrno(ctx context.Context, sockfd, len, flags int32) (int32, []byte, error) { - dut.t.Helper() - req := pb.RecvRequest{ - Sockfd: sockfd, - Len: len, - Flags: flags, - } - resp, err := dut.posixServer.Recv(ctx, &req) - if err != nil { - dut.t.Fatalf("failed to call Recv: %s", err) - } - return resp.GetRet(), resp.GetBuf(), syscall.Errno(resp.GetErrno_()) -} diff --git a/test/packetimpact/testbench/dut_client.go b/test/packetimpact/testbench/dut_client.go deleted file mode 100644 index b130a33a2..000000000 --- a/test/packetimpact/testbench/dut_client.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package testbench - -import ( - "google.golang.org/grpc" - pb "gvisor.dev/gvisor/test/packetimpact/proto/posix_server_go_proto" -) - -// PosixClient is a gRPC client for the Posix service. -type PosixClient pb.PosixClient - -// NewPosixClient makes a new gRPC client for the Posix service. -func NewPosixClient(c grpc.ClientConnInterface) PosixClient { - return pb.NewPosixClient(c) -} diff --git a/test/packetimpact/testbench/layers.go b/test/packetimpact/testbench/layers.go deleted file mode 100644 index b467c15cc..000000000 --- a/test/packetimpact/testbench/layers.go +++ /dev/null @@ -1,671 +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" - "reflect" - "strings" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/imdario/mergo" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/buffer" - "gvisor.dev/gvisor/pkg/tcpip/header" -) - -// 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) -} - -// LayerBase is the common elements of all layers. -type LayerBase struct { - nextLayer Layer - prevLayer Layer -} - -func (lb *LayerBase) next() Layer { - return lb.nextLayer -} - -func (lb *LayerBase) prev() Layer { - return lb.prevLayer -} - -func (lb *LayerBase) setNext(l Layer) { - lb.nextLayer = l -} - -func (lb *LayerBase) setPrev(l Layer) { - lb.prevLayer = l -} - -// equalLayer compares that two Layer structs match while ignoring field in -// which either input has a nil and also ignoring the LayerBase of the inputs. -func equalLayer(x, y Layer) bool { - // 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{})) -} - -func stringLayer(l Layer) string { - v := reflect.ValueOf(l).Elem() - t := v.Type() - var ret []string - for i := 0; i < v.NumField(); i++ { - t := t.Field(i) - if t.Anonymous { - // Ignore the LayerBase in the Layer struct. - continue - } - v := v.Field(i) - if v.IsNil() { - continue - } - ret = append(ret, fmt.Sprintf("%s:%v", t.Name, reflect.Indirect(v))) - } - return fmt.Sprintf("&%s{%s}", t, strings.Join(ret, " ")) -} - -// Ether can construct and match an ethernet encapsulation. -type Ether struct { - LayerBase - SrcAddr *tcpip.LinkAddress - DstAddr *tcpip.LinkAddress - Type *tcpip.NetworkProtocolNumber -} - -func (l *Ether) String() string { - return stringLayer(l) -} - -func (l *Ether) toBytes() ([]byte, error) { - b := make([]byte, header.EthernetMinimumSize) - h := header.Ethernet(b) - 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 - default: - // TODO(b/150301488): Support more protocols, like IPv6. - 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 - 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 -} - -// IPv4 can construct and match an IPv4 encapsulation. -type IPv4 struct { - LayerBase - IHL *uint8 - TOS *uint8 - TotalLength *uint16 - ID *uint16 - Flags *uint8 - FragmentOffset *uint16 - TTL *uint8 - Protocol *uint8 - Checksum *uint16 - SrcAddr *tcpip.Address - DstAddr *tcpip.Address -} - -func (l *IPv4) String() string { - return stringLayer(l) -} - -func (l *IPv4) toBytes() ([]byte, error) { - b := make([]byte, header.IPv4MinimumSize) - h := header.IPv4(b) - fields := &header.IPv4Fields{ - IHL: 20, - TOS: 0, - TotalLength: 0, - ID: 0, - Flags: 0, - FragmentOffset: 0, - TTL: 64, - Protocol: 0, - Checksum: 0, - SrcAddr: tcpip.Address(""), - DstAddr: tcpip.Address(""), - } - if l.TOS != nil { - fields.TOS = *l.TOS - } - if l.TotalLength != nil { - fields.TotalLength = *l.TotalLength - } else { - fields.TotalLength = uint16(l.length()) - current := l.next() - for current != nil { - fields.TotalLength += uint16(current.length()) - current = current.next() - } - } - if l.ID != nil { - fields.ID = *l.ID - } - if l.Flags != nil { - fields.Flags = *l.Flags - } - if l.FragmentOffset != nil { - fields.FragmentOffset = *l.FragmentOffset - } - if l.TTL != nil { - fields.TTL = *l.TTL - } - if l.Protocol != nil { - fields.Protocol = *l.Protocol - } else { - switch n := l.next().(type) { - case *TCP: - fields.Protocol = uint8(header.TCPProtocolNumber) - case *UDP: - fields.Protocol = uint8(header.UDPProtocolNumber) - default: - // TODO(b/150301488): Support more protocols as needed. - return nil, fmt.Errorf("ipv4 header's next layer is unrecognized: %#v", n) - } - } - if l.SrcAddr != nil { - fields.SrcAddr = *l.SrcAddr - } - if l.DstAddr != nil { - fields.DstAddr = *l.DstAddr - } - if l.Checksum != nil { - fields.Checksum = *l.Checksum - } - h.Encode(fields) - if l.Checksum == nil { - h.SetChecksum(^h.CalculateChecksum()) - } - return h, nil -} - -// Uint16 is a helper routine that allocates a new -// uint16 value to store v and returns a pointer to it. -func Uint16(v uint16) *uint16 { - return &v -} - -// Uint8 is a helper routine that allocates a new -// uint8 value to store v and returns a pointer to it. -func Uint8(v uint8) *uint8 { - return &v -} - -// Address is a helper routine that allocates a new tcpip.Address value to store -// v and returns a pointer to it. -func Address(v tcpip.Address) *tcpip.Address { - return &v -} - -// ParseIPv4 parses the bytes assuming that they start with an ipv4 header and -// continues parsing further encapsulations. -func ParseIPv4(b []byte) (Layer, LayerParser) { - h := header.IPv4(b) - tos, _ := h.TOS() - ipv4 := IPv4{ - IHL: Uint8(h.HeaderLength()), - TOS: &tos, - TotalLength: Uint16(h.TotalLength()), - ID: Uint16(h.ID()), - Flags: Uint8(h.Flags()), - FragmentOffset: Uint16(h.FragmentOffset()), - TTL: Uint8(h.TTL()), - Protocol: Uint8(h.Protocol()), - Checksum: Uint16(h.Checksum()), - SrcAddr: Address(h.SourceAddress()), - DstAddr: Address(h.DestinationAddress()), - } - var nextParser LayerParser - switch h.TransportProtocol() { - case header.TCPProtocolNumber: - nextParser = ParseTCP - case header.UDPProtocolNumber: - nextParser = ParseUDP - default: - // Assume that the rest is a payload. - nextParser = ParsePayload - } - return &ipv4, nextParser -} - -func (l *IPv4) match(other Layer) bool { - return equalLayer(l, other) -} - -func (l *IPv4) length() int { - if l.IHL == nil { - return header.IPv4MinimumSize - } - return int(*l.IHL) -} - -// TCP can construct and match a TCP encapsulation. -type TCP struct { - LayerBase - SrcPort *uint16 - DstPort *uint16 - SeqNum *uint32 - AckNum *uint32 - DataOffset *uint8 - Flags *uint8 - WindowSize *uint16 - Checksum *uint16 - UrgentPointer *uint16 -} - -func (l *TCP) String() string { - return stringLayer(l) -} - -func (l *TCP) toBytes() ([]byte, error) { - b := make([]byte, header.TCPMinimumSize) - h := header.TCP(b) - if l.SrcPort != nil { - h.SetSourcePort(*l.SrcPort) - } - if l.DstPort != nil { - h.SetDestinationPort(*l.DstPort) - } - if l.SeqNum != nil { - h.SetSequenceNumber(*l.SeqNum) - } - if l.AckNum != nil { - h.SetAckNumber(*l.AckNum) - } - if l.DataOffset != nil { - h.SetDataOffset(*l.DataOffset) - } else { - h.SetDataOffset(uint8(l.length())) - } - if l.Flags != nil { - h.SetFlags(*l.Flags) - } - if l.WindowSize != nil { - h.SetWindowSize(*l.WindowSize) - } else { - h.SetWindowSize(32768) - } - if l.UrgentPointer != nil { - h.SetUrgentPoiner(*l.UrgentPointer) - } - if l.Checksum != nil { - h.SetChecksum(*l.Checksum) - return h, nil - } - if err := setTCPChecksum(&h, l); err != nil { - return nil, err - } - return h, nil -} - -// totalLength returns the length of the provided layer and all following -// layers. -func totalLength(l Layer) int { - var totalLength int - for ; l != nil; l = l.next() { - totalLength += l.length() - } - return totalLength -} - -// layerChecksum calculates the checksum of the Layer header, including the -// peusdeochecksum of the layer before it and all the bytes after it.. -func layerChecksum(l Layer, protoNumber tcpip.TransportProtocolNumber) (uint16, error) { - totalLength := uint16(totalLength(l)) - var xsum uint16 - switch s := l.prev().(type) { - case *IPv4: - xsum = header.PseudoHeaderChecksum(protoNumber, *s.SrcAddr, *s.DstAddr, totalLength) - default: - // TODO(b/150301488): Support more protocols, like IPv6. - return 0, fmt.Errorf("can't get src and dst addr from previous layer: %#v", s) - } - var payloadBytes buffer.VectorisedView - for current := l.next(); current != nil; current = current.next() { - payload, err := current.toBytes() - if err != nil { - return 0, fmt.Errorf("can't get bytes for next header: %s", payload) - } - payloadBytes.AppendView(payload) - } - xsum = header.ChecksumVV(payloadBytes, xsum) - return xsum, nil -} - -// setTCPChecksum calculates the checksum of the TCP header and sets it in h. -func setTCPChecksum(h *header.TCP, tcp *TCP) error { - h.SetChecksum(0) - xsum, err := layerChecksum(tcp, header.TCPProtocolNumber) - if err != nil { - return err - } - h.SetChecksum(^h.CalculateChecksum(xsum)) - return nil -} - -// Uint32 is a helper routine that allocates a new -// uint32 value to store v and returns a pointer to it. -func Uint32(v uint32) *uint32 { - return &v -} - -// ParseTCP parses the bytes assuming that they start with a tcp header and -// continues parsing further encapsulations. -func ParseTCP(b []byte) (Layer, LayerParser) { - h := header.TCP(b) - tcp := TCP{ - SrcPort: Uint16(h.SourcePort()), - DstPort: Uint16(h.DestinationPort()), - SeqNum: Uint32(h.SequenceNumber()), - AckNum: Uint32(h.AckNumber()), - DataOffset: Uint8(h.DataOffset()), - Flags: Uint8(h.Flags()), - WindowSize: Uint16(h.WindowSize()), - Checksum: Uint16(h.Checksum()), - UrgentPointer: Uint16(h.UrgentPointer()), - } - return &tcp, ParsePayload -} - -func (l *TCP) match(other Layer) bool { - return equalLayer(l, other) -} - -func (l *TCP) length() int { - if l.DataOffset == nil { - return header.TCPMinimumSize - } - return int(*l.DataOffset) -} - -// merge overrides the values in l with the values from other but only in fields -// where the value is not nil. -func (l *TCP) merge(other TCP) error { - return mergo.Merge(l, other, mergo.WithOverride) -} - -// UDP can construct and match a UDP encapsulation. -type UDP struct { - LayerBase - SrcPort *uint16 - DstPort *uint16 - Length *uint16 - Checksum *uint16 -} - -func (l *UDP) String() string { - return stringLayer(l) -} - -func (l *UDP) toBytes() ([]byte, error) { - b := make([]byte, header.UDPMinimumSize) - h := header.UDP(b) - if l.SrcPort != nil { - h.SetSourcePort(*l.SrcPort) - } - if l.DstPort != nil { - h.SetDestinationPort(*l.DstPort) - } - if l.Length != nil { - h.SetLength(*l.Length) - } else { - h.SetLength(uint16(totalLength(l))) - } - if l.Checksum != nil { - h.SetChecksum(*l.Checksum) - return h, nil - } - if err := setUDPChecksum(&h, l); err != nil { - return nil, err - } - return h, nil -} - -// setUDPChecksum calculates the checksum of the UDP header and sets it in h. -func setUDPChecksum(h *header.UDP, udp *UDP) error { - h.SetChecksum(0) - xsum, err := layerChecksum(udp, header.UDPProtocolNumber) - if err != nil { - return err - } - h.SetChecksum(^h.CalculateChecksum(xsum)) - return nil -} - -// ParseUDP parses the bytes assuming that they start with a udp header and -// returns the parsed layer and the next parser to use. -func ParseUDP(b []byte) (Layer, LayerParser) { - h := header.UDP(b) - udp := UDP{ - SrcPort: Uint16(h.SourcePort()), - DstPort: Uint16(h.DestinationPort()), - Length: Uint16(h.Length()), - Checksum: Uint16(h.Checksum()), - } - return &udp, ParsePayload -} - -func (l *UDP) match(other Layer) bool { - return equalLayer(l, other) -} - -func (l *UDP) length() int { - if l.Length == nil { - return header.UDPMinimumSize - } - return int(*l.Length) -} - -// merge overrides the values in l with the values from other but only in fields -// where the value is not nil. -func (l *UDP) merge(other UDP) error { - return mergo.Merge(l, other, mergo.WithOverride) -} - -// Payload has bytes beyond OSI layer 4. -type Payload struct { - LayerBase - Bytes []byte -} - -func (l *Payload) String() string { - return stringLayer(l) -} - -// ParsePayload parses the bytes assuming that they start with a payload and -// continue to the end. There can be no further encapsulations. -func ParsePayload(b []byte) (Layer, LayerParser) { - payload := Payload{ - Bytes: b, - } - return &payload, nil -} - -func (l *Payload) toBytes() ([]byte, error) { - return l.Bytes, nil -} - -func (l *Payload) match(other Layer) bool { - return equalLayer(l, other) -} - -func (l *Payload) length() int { - return len(l.Bytes) -} - -// 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) - } - } -} - -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 := 0; i < len(*ls); i++ { - if !equalLayer((*ls)[i], other[i]) { - return false - } - } - return true -} diff --git a/test/packetimpact/testbench/layers_test.go b/test/packetimpact/testbench/layers_test.go deleted file mode 100644 index 8ffc26bf9..000000000 --- a/test/packetimpact/testbench/layers_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 testbench - -import "testing" - -import "gvisor.dev/gvisor/pkg/tcpip" - -func TestLayerMatch(t *testing.T) { - var nilPayload *Payload - noPayload := &Payload{} - emptyPayload := &Payload{Bytes: []byte{}} - fullPayload := &Payload{Bytes: []byte{1, 2, 3}} - emptyTCP := &TCP{SrcPort: Uint16(1234), LayerBase: LayerBase{nextLayer: emptyPayload}} - fullTCP := &TCP{SrcPort: Uint16(1234), LayerBase: LayerBase{nextLayer: fullPayload}} - for _, tt := range []struct { - a, b Layer - want bool - }{ - {nilPayload, nilPayload, true}, - {nilPayload, noPayload, true}, - {nilPayload, emptyPayload, true}, - {nilPayload, fullPayload, true}, - {noPayload, noPayload, true}, - {noPayload, emptyPayload, true}, - {noPayload, fullPayload, true}, - {emptyPayload, emptyPayload, true}, - {emptyPayload, fullPayload, false}, - {fullPayload, fullPayload, true}, - {emptyTCP, fullTCP, true}, - } { - if got := tt.a.match(tt.b); got != tt.want { - t.Errorf("%s.match(%s) = %t, want %t", tt.a, tt.b, got, tt.want) - } - if got := tt.b.match(tt.a); got != tt.want { - t.Errorf("%s.match(%s) = %t, want %t", tt.b, tt.a, got, tt.want) - } - } -} - -func TestLayerStringFormat(t *testing.T) { - for _, tt := range []struct { - name string - l Layer - want string - }{ - { - name: "TCP", - l: &TCP{ - SrcPort: Uint16(34785), - DstPort: Uint16(47767), - SeqNum: Uint32(3452155723), - AckNum: Uint32(2596996163), - DataOffset: Uint8(5), - Flags: Uint8(20), - WindowSize: Uint16(64240), - Checksum: Uint16(0x2e2b), - }, - want: "&testbench.TCP{" + - "SrcPort:34785 " + - "DstPort:47767 " + - "SeqNum:3452155723 " + - "AckNum:2596996163 " + - "DataOffset:5 " + - "Flags:20 " + - "WindowSize:64240 " + - "Checksum:11819" + - "}", - }, - { - name: "UDP", - l: &UDP{ - SrcPort: Uint16(34785), - DstPort: Uint16(47767), - Length: Uint16(12), - }, - want: "&testbench.UDP{" + - "SrcPort:34785 " + - "DstPort:47767 " + - "Length:12" + - "}", - }, - { - name: "IPv4", - l: &IPv4{ - IHL: Uint8(5), - TOS: Uint8(0), - TotalLength: Uint16(44), - ID: Uint16(0), - Flags: Uint8(2), - FragmentOffset: Uint16(0), - TTL: Uint8(64), - Protocol: Uint8(6), - Checksum: Uint16(0x2e2b), - SrcAddr: Address(tcpip.Address([]byte{197, 34, 63, 10})), - DstAddr: Address(tcpip.Address([]byte{197, 34, 63, 20})), - }, - want: "&testbench.IPv4{" + - "IHL:5 " + - "TOS:0 " + - "TotalLength:44 " + - "ID:0 " + - "Flags:2 " + - "FragmentOffset:0 " + - "TTL:64 " + - "Protocol:6 " + - "Checksum:11819 " + - "SrcAddr:197.34.63.10 " + - "DstAddr:197.34.63.20" + - "}", - }, - { - name: "Ether", - l: &Ether{ - SrcAddr: LinkAddress(tcpip.LinkAddress([]byte{0x02, 0x42, 0xc5, 0x22, 0x3f, 0x0a})), - DstAddr: LinkAddress(tcpip.LinkAddress([]byte{0x02, 0x42, 0xc5, 0x22, 0x3f, 0x14})), - Type: NetworkProtocolNumber(4), - }, - want: "&testbench.Ether{" + - "SrcAddr:02:42:c5:22:3f:0a " + - "DstAddr:02:42:c5:22:3f:14 " + - "Type:4" + - "}", - }, - } { - t.Run(tt.name, func(t *testing.T) { - if got := tt.l.String(); got != tt.want { - t.Errorf("%s.String() = %s, want: %s", tt.name, got, tt.want) - } - }) - } -} diff --git a/test/packetimpact/testbench/rawsockets.go b/test/packetimpact/testbench/rawsockets.go deleted file mode 100644 index 09bfa43c5..000000000 --- a/test/packetimpact/testbench/rawsockets.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 - -import ( - "encoding/binary" - "flag" - "math" - "net" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/usermem" -) - -var device = flag.String("device", "", "local device for test packets") - -// Sniffer can sniff raw packets on the wire. -type Sniffer struct { - t *testing.T - fd int -} - -func htons(x uint16) uint16 { - buf := [2]byte{} - binary.BigEndian.PutUint16(buf[:], x) - return usermem.ByteOrder.Uint16(buf[:]) -} - -// NewSniffer creates a Sniffer connected to *device. -func NewSniffer(t *testing.T) (Sniffer, error) { - flag.Parse() - snifferFd, err := unix.Socket(unix.AF_PACKET, unix.SOCK_RAW, int(htons(unix.ETH_P_ALL))) - if err != nil { - return Sniffer{}, err - } - if err := unix.SetsockoptInt(snifferFd, unix.SOL_SOCKET, unix.SO_RCVBUFFORCE, 1); err != nil { - t.Fatalf("can't set sockopt SO_RCVBUFFORCE to 1: %s", err) - } - if err := unix.SetsockoptInt(snifferFd, unix.SOL_SOCKET, unix.SO_RCVBUF, 1e7); err != nil { - t.Fatalf("can't setsockopt SO_RCVBUF to 10M: %s", err) - } - return Sniffer{ - t: t, - fd: snifferFd, - }, nil -} - -// maxReadSize should be large enough for the maximum frame size in bytes. If a -// packet too large for the buffer arrives, the test will get a fatal error. -const maxReadSize int = 65536 - -// Recv tries to read one frame until the timeout is up. -func (s *Sniffer) Recv(timeout time.Duration) []byte { - deadline := time.Now().Add(timeout) - for { - timeout = deadline.Sub(time.Now()) - if timeout <= 0 { - return nil - } - whole, frac := math.Modf(timeout.Seconds()) - tv := unix.Timeval{ - Sec: int64(whole), - Usec: int64(frac * float64(time.Microsecond/time.Second)), - } - - if err := unix.SetsockoptTimeval(s.fd, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &tv); err != nil { - s.t.Fatalf("can't setsockopt SO_RCVTIMEO: %s", err) - } - - buf := make([]byte, maxReadSize) - nread, _, err := unix.Recvfrom(s.fd, buf, unix.MSG_TRUNC) - if err == unix.EINTR || err == unix.EAGAIN { - // There was a timeout. - continue - } - if err != nil { - s.t.Fatalf("can't read: %s", err) - } - if nread > maxReadSize { - s.t.Fatalf("received a truncated frame of %d bytes", nread) - } - return buf[:nread] - } -} - -// Drain drains the Sniffer's socket receive buffer by receiving until there's -// nothing else to receive. -func (s *Sniffer) Drain() { - s.t.Helper() - flags, err := unix.FcntlInt(uintptr(s.fd), unix.F_GETFL, 0) - if err != nil { - s.t.Fatalf("failed to get sniffer socket fd flags: %s", err) - } - if _, err := unix.FcntlInt(uintptr(s.fd), unix.F_SETFL, flags|unix.O_NONBLOCK); err != nil { - s.t.Fatalf("failed to make sniffer socket non-blocking: %s", err) - } - for { - buf := make([]byte, maxReadSize) - _, _, err := unix.Recvfrom(s.fd, buf, unix.MSG_TRUNC) - if err == unix.EINTR || err == unix.EAGAIN || err == unix.EWOULDBLOCK { - break - } - } - if _, err := unix.FcntlInt(uintptr(s.fd), unix.F_SETFL, flags); err != nil { - s.t.Fatalf("failed to restore sniffer socket fd flags: %s", err) - } -} - -// Close the socket that Sniffer is using. -func (s *Sniffer) Close() { - if err := unix.Close(s.fd); err != nil { - s.t.Fatalf("can't close sniffer socket: %s", err) - } - s.fd = -1 -} - -// Injector can inject raw frames. -type Injector struct { - t *testing.T - fd int -} - -// NewInjector creates a new injector on *device. -func NewInjector(t *testing.T) (Injector, error) { - flag.Parse() - ifInfo, err := net.InterfaceByName(*device) - if err != nil { - return Injector{}, err - } - - var haddr [8]byte - copy(haddr[:], ifInfo.HardwareAddr) - sa := unix.SockaddrLinklayer{ - Protocol: unix.ETH_P_IP, - Ifindex: ifInfo.Index, - Halen: uint8(len(ifInfo.HardwareAddr)), - Addr: haddr, - } - - injectFd, err := unix.Socket(unix.AF_PACKET, unix.SOCK_RAW, int(htons(unix.ETH_P_ALL))) - if err != nil { - return Injector{}, err - } - if err := unix.Bind(injectFd, &sa); err != nil { - return Injector{}, err - } - return Injector{ - t: t, - fd: injectFd, - }, nil -} - -// Send a raw frame. -func (i *Injector) Send(b []byte) { - if _, err := unix.Write(i.fd, b); err != nil { - i.t.Fatalf("can't write: %s", err) - } -} - -// Close the underlying socket. -func (i *Injector) Close() { - if err := unix.Close(i.fd); err != nil { - i.t.Fatalf("can't close sniffer socket: %s", err) - } - i.fd = -1 -} diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD deleted file mode 100644 index 308590162..000000000 --- a/test/packetimpact/tests/BUILD +++ /dev/null @@ -1,70 +0,0 @@ -load("defs.bzl", "packetimpact_go_test") - -package( - default_visibility = ["//test/packetimpact:__subpackages__"], - licenses = ["notice"], -) - -packetimpact_go_test( - name = "fin_wait2_timeout", - srcs = ["fin_wait2_timeout_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_go_test( - name = "udp_recv_multicast", - srcs = ["udp_recv_multicast_test.go"], - # TODO(b/152813495): Fix netstack then remove the line below. - netstack = False, - deps = [ - "//pkg/tcpip", - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_go_test( - name = "tcp_window_shrink", - srcs = ["tcp_window_shrink_test.go"], - # TODO(b/153202472): Fix netstack then remove the line below. - netstack = False, - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_go_test( - name = "tcp_noaccept_close_rst", - srcs = ["tcp_noaccept_close_rst_test.go"], - # TODO(b/153380909): Fix netstack then remove the line below. - netstack = False, - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_go_test( - name = "tcp_should_piggyback", - srcs = ["tcp_should_piggyback_test.go"], - # TODO(b/153680566): Fix netstack then remove the line below. - netstack = False, - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -sh_binary( - name = "test_runner", - srcs = ["test_runner.sh"], -) diff --git a/test/packetimpact/tests/Dockerfile b/test/packetimpact/tests/Dockerfile deleted file mode 100644 index 9075bc555..000000000 --- a/test/packetimpact/tests/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -FROM ubuntu:bionic - -RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \ - # iptables to disable OS native packet processing. - iptables \ - # nc to check that the posix_server is running. - netcat \ - # tcpdump to log brief packet sniffing. - tcpdump \ - # ip link show to display MAC addresses. - iproute2 \ - # tshark to log verbose packet sniffing. - tshark \ - # killall for cleanup. - psmisc -RUN hash -r -CMD /bin/bash diff --git a/test/packetimpact/tests/defs.bzl b/test/packetimpact/tests/defs.bzl deleted file mode 100644 index 8c0d058b2..000000000 --- a/test/packetimpact/tests/defs.bzl +++ /dev/null @@ -1,118 +0,0 @@ -"""Defines rules for packetimpact test targets.""" - -load("//tools:defs.bzl", "go_test") - -def _packetimpact_test_impl(ctx): - test_runner = ctx.executable._test_runner - bench = ctx.actions.declare_file("%s-bench" % ctx.label.name) - bench_content = "\n".join([ - "#!/bin/bash", - # This test will run part in a distinct user namespace. This can cause - # permission problems, because all runfiles may not be owned by the - # current user, and no other users will be mapped in that namespace. - # Make sure that everything is readable here. - "find . -type f -exec chmod a+rx {} \\;", - "find . -type d -exec chmod a+rx {} \\;", - "%s %s --posix_server_binary %s --testbench_binary %s $@\n" % ( - test_runner.short_path, - " ".join(ctx.attr.flags), - ctx.files._posix_server_binary[0].short_path, - ctx.files.testbench_binary[0].short_path, - ), - ]) - ctx.actions.write(bench, bench_content, is_executable = True) - - transitive_files = depset() - if hasattr(ctx.attr._test_runner, "data_runfiles"): - transitive_files = depset(ctx.attr._test_runner.data_runfiles.files) - runfiles = ctx.runfiles( - files = [test_runner] + ctx.files.testbench_binary + ctx.files._posix_server_binary, - transitive_files = transitive_files, - collect_default = True, - collect_data = True, - ) - return [DefaultInfo(executable = bench, runfiles = runfiles)] - -_packetimpact_test = rule( - attrs = { - "_test_runner": attr.label( - executable = True, - cfg = "target", - default = ":test_runner", - ), - "_posix_server_binary": attr.label( - cfg = "target", - default = "//test/packetimpact/dut:posix_server", - ), - "testbench_binary": attr.label( - cfg = "target", - mandatory = True, - ), - "flags": attr.string_list( - mandatory = False, - default = [], - ), - }, - test = True, - implementation = _packetimpact_test_impl, -) - -PACKETIMPACT_TAGS = ["local", "manual"] - -def packetimpact_linux_test(name, testbench_binary, **kwargs): - """Add a packetimpact test on linux. - - Args: - name: name of the test - testbench_binary: the testbench binary - **kwargs: all the other args, forwarded to _packetimpact_test - """ - _packetimpact_test( - name = name + "_linux_test", - testbench_binary = testbench_binary, - flags = ["--dut_platform", "linux"], - tags = PACKETIMPACT_TAGS + ["packetimpact"], - **kwargs - ) - -def packetimpact_netstack_test(name, testbench_binary, **kwargs): - """Add a packetimpact test on netstack. - - Args: - name: name of the test - testbench_binary: the testbench binary - **kwargs: all the other args, forwarded to _packetimpact_test - """ - _packetimpact_test( - name = name + "_netstack_test", - testbench_binary = testbench_binary, - # This is the default runtime unless - # "--test_arg=--runtime=OTHER_RUNTIME" is used to override the value. - flags = ["--dut_platform", "netstack", "--runtime=runsc-d"], - tags = PACKETIMPACT_TAGS + ["packetimpact"], - **kwargs - ) - -def packetimpact_go_test(name, size = "small", pure = True, linux = True, netstack = True, **kwargs): - """Add packetimpact tests written in go. - - Args: - name: name of the test - size: size of the test - pure: make a static go binary - linux: generate a linux test - netstack: generate a netstack test - **kwargs: all the other args, forwarded to go_test - """ - testbench_binary = name + "_test" - go_test( - name = testbench_binary, - size = size, - pure = pure, - tags = PACKETIMPACT_TAGS, - **kwargs - ) - if linux: - packetimpact_linux_test(name = name, testbench_binary = testbench_binary) - if netstack: - packetimpact_netstack_test(name = name, testbench_binary = testbench_binary) diff --git a/test/packetimpact/tests/fin_wait2_timeout_test.go b/test/packetimpact/tests/fin_wait2_timeout_test.go deleted file mode 100644 index b98594f94..000000000 --- a/test/packetimpact/tests/fin_wait2_timeout_test.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package fin_wait2_timeout_test - -import ( - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - tb "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func TestFinWait2Timeout(t *testing.T) { - for _, tt := range []struct { - description string - linger2 bool - }{ - {"WithLinger2", true}, - {"WithoutLinger2", false}, - } { - t.Run(tt.description, func(t *testing.T) { - dut := tb.NewDUT(t) - defer dut.TearDown() - listenFd, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(listenFd) - conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort}, tb.TCP{SrcPort: &remotePort}) - defer conn.Close() - conn.Handshake() - - acceptFd, _ := dut.Accept(listenFd) - if tt.linger2 { - tv := unix.Timeval{Sec: 1, Usec: 0} - dut.SetSockOptTimeval(acceptFd, unix.SOL_TCP, unix.TCP_LINGER2, &tv) - } - dut.Close(acceptFd) - - if _, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { - t.Fatalf("expected a FIN-ACK within 1 second but got none: %s", err) - } - conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)}) - - time.Sleep(5 * time.Second) - conn.Drain() - - conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)}) - if tt.linger2 { - if _, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagRst)}, time.Second); err != nil { - t.Fatalf("expected a RST packet within a second but got none: %s", err) - } - } else { - if _, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagRst)}, 10*time.Second); err == nil { - t.Fatalf("expected no RST packets within ten seconds but got one: %s", err) - } - } - }) - } -} diff --git a/test/packetimpact/tests/tcp_noaccept_close_rst_test.go b/test/packetimpact/tests/tcp_noaccept_close_rst_test.go deleted file mode 100644 index 7ebdd1950..000000000 --- a/test/packetimpact/tests/tcp_noaccept_close_rst_test.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcp_noaccept_close_rst_test - -import ( - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - tb "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func TestTcpNoAcceptCloseReset(t *testing.T) { - dut := tb.NewDUT(t) - defer dut.TearDown() - listenFd, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort}, tb.TCP{SrcPort: &remotePort}) - conn.Handshake() - defer conn.Close() - dut.Close(listenFd) - if _, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagRst | header.TCPFlagAck)}, 1*time.Second); err != nil { - t.Fatalf("expected a RST-ACK packet but got none: %s", err) - } -} diff --git a/test/packetimpact/tests/tcp_should_piggyback_test.go b/test/packetimpact/tests/tcp_should_piggyback_test.go deleted file mode 100644 index f2ab49e51..000000000 --- a/test/packetimpact/tests/tcp_should_piggyback_test.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcp_should_piggyback_test - -import ( - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - tb "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func TestPiggyback(t *testing.T) { - dut := tb.NewDUT(t) - defer dut.TearDown() - listenFd, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(listenFd) - conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort, WindowSize: tb.Uint16(12)}, tb.TCP{SrcPort: &remotePort}) - defer conn.Close() - - conn.Handshake() - acceptFd, _ := dut.Accept(listenFd) - defer dut.Close(acceptFd) - - dut.SetSockOptInt(acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1) - - sampleData := []byte("Sample Data") - - dut.Send(acceptFd, sampleData, 0) - conn.ExpectData(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, sampleData, time.Second) - - // Cause DUT to send us more data as soon as we ACK their first data segment because we have - // a small window. - dut.Send(acceptFd, sampleData, 0) - - // DUT should ACK our segment by piggybacking ACK to their outstanding data segment instead of - // sending a separate ACK packet. - conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, &tb.Payload{Bytes: sampleData}) - conn.ExpectData(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, sampleData, time.Second) -} 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 b48cc6491..000000000 --- a/test/packetimpact/tests/tcp_window_shrink_test.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcp_window_shrink_test - -import ( - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - tb "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func TestWindowShrink(t *testing.T) { - dut := tb.NewDUT(t) - defer dut.TearDown() - listenFd, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(listenFd) - conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort}, tb.TCP{SrcPort: &remotePort}) - defer conn.Close() - - conn.Handshake() - acceptFd, _ := dut.Accept(listenFd) - defer dut.Close(acceptFd) - - dut.SetSockOptInt(acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1) - - sampleData := []byte("Sample Data") - - dut.Send(acceptFd, sampleData, 0) - conn.ExpectData(tb.TCP{}, sampleData, time.Second) - conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)}) - - dut.Send(acceptFd, sampleData, 0) - dut.Send(acceptFd, sampleData, 0) - conn.ExpectData(tb.TCP{}, sampleData, time.Second) - conn.ExpectData(tb.TCP{}, sampleData, time.Second) - // We close our receiving window here - conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), WindowSize: tb.Uint16(0)}) - - dut.Send(acceptFd, []byte("Sample Data"), 0) - // Note: There is another kind of zero-window probing which Windows uses (by sending one - // new byte at `RemoteSeqNum`), if netstack wants to go that way, we may want to change - // the following lines. - conn.ExpectData(tb.TCP{SeqNum: tb.Uint32(uint32(conn.RemoteSeqNum - 1))}, nil, time.Second) -} diff --git a/test/packetimpact/tests/test_runner.sh b/test/packetimpact/tests/test_runner.sh deleted file mode 100755 index e99fc7d09..000000000 --- a/test/packetimpact/tests/test_runner.sh +++ /dev/null @@ -1,268 +0,0 @@ -#!/bin/bash - -# Copyright 2020 The gVisor Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Run a packetimpact test. Two docker containers are made, one for the -# Device-Under-Test (DUT) and one for the test bench. Each is attached with -# two networks, one for control packets that aid the test and one for test -# packets which are sent as part of the test and observed for correctness. - -set -euxo pipefail - -function failure() { - local lineno=$1 - local msg=$2 - local filename="$0" - echo "FAIL: $filename:$lineno: $msg" -} -trap 'failure ${LINENO} "$BASH_COMMAND"' ERR - -declare -r LONGOPTS="dut_platform:,posix_server_binary:,testbench_binary:,runtime:,tshark,extra_test_arg:" - -# Don't use declare below so that the error from getopt will end the script. -PARSED=$(getopt --options "" --longoptions=$LONGOPTS --name "$0" -- "$@") - -eval set -- "$PARSED" - -declare -a EXTRA_TEST_ARGS - -while true; do - case "$1" in - --dut_platform) - # Either "linux" or "netstack". - declare -r DUT_PLATFORM="$2" - shift 2 - ;; - --posix_server_binary) - declare -r POSIX_SERVER_BINARY="$2" - shift 2 - ;; - --testbench_binary) - declare -r TESTBENCH_BINARY="$2" - shift 2 - ;; - --runtime) - # Not readonly because there might be multiple --runtime arguments and we - # want to use just the last one. Only used if --dut_platform is - # "netstack". - declare RUNTIME="$2" - shift 2 - ;; - --tshark) - declare -r TSHARK="1" - shift 1 - ;; - --extra_test_arg) - EXTRA_TEST_ARGS+="$2" - shift 2 - ;; - --) - shift - break - ;; - *) - echo "Programming error" - exit 3 - esac -done - -# All the other arguments are scripts. -declare -r scripts="$@" - -# Check that the required flags are defined in a way that is safe for "set -u". -if [[ "${DUT_PLATFORM-}" == "netstack" ]]; then - if [[ -z "${RUNTIME-}" ]]; then - echo "FAIL: Missing --runtime argument: ${RUNTIME-}" - exit 2 - fi - declare -r RUNTIME_ARG="--runtime ${RUNTIME}" -elif [[ "${DUT_PLATFORM-}" == "linux" ]]; then - declare -r RUNTIME_ARG="" -else - echo "FAIL: Bad or missing --dut_platform argument: ${DUT_PLATFORM-}" - exit 2 -fi -if [[ ! -f "${POSIX_SERVER_BINARY-}" ]]; then - echo "FAIL: Bad or missing --posix_server_binary: ${POSIX_SERVER-}" - exit 2 -fi -if [[ ! -f "${TESTBENCH_BINARY-}" ]]; then - echo "FAIL: Bad or missing --testbench_binary: ${TESTBENCH_BINARY-}" - exit 2 -fi - -# Variables specific to the control network and interface start with CTRL_. -# Variables specific to the test network and interface start with TEST_. -# Variables specific to the DUT start with DUT_. -# Variables specific to the test bench start with TESTBENCH_. -# Use random numbers so that test networks don't collide. -declare -r CTRL_NET="ctrl_net-${RANDOM}${RANDOM}" -declare -r TEST_NET="test_net-${RANDOM}${RANDOM}" -# On both DUT and test bench, testing packets are on the eth2 interface. -declare -r TEST_DEVICE="eth2" -# Number of bits in the *_NET_PREFIX variables. -declare -r NET_MASK="24" -function new_net_prefix() { - # Class C, 192.0.0.0 to 223.255.255.255, transitionally has mask 24. - echo "$(shuf -i 192-223 -n 1).$(shuf -i 0-255 -n 1).$(shuf -i 0-255 -n 1)" -} -# Last bits of the DUT's IP address. -declare -r DUT_NET_SUFFIX=".10" -# Control port. -declare -r CTRL_PORT="40000" -# Last bits of the test bench's IP address. -declare -r TESTBENCH_NET_SUFFIX=".20" -declare -r TIMEOUT="60" -declare -r IMAGE_TAG="gcr.io/gvisor-presubmit/packetimpact" -# Make sure that docker is installed. -docker --version - -function finish { - local cleanup_success=1 - - if [[ -z "${TSHARK-}" ]]; then - # Kill tcpdump so that it will flush output. - docker exec -t "${TESTBENCH}" \ - killall tcpdump || \ - cleanup_success=0 - else - # Kill tshark so that it will flush output. - docker exec -t "${TESTBENCH}" \ - killall tshark || \ - cleanup_success=0 - fi - - for net in "${CTRL_NET}" "${TEST_NET}"; do - # Kill all processes attached to ${net}. - for docker_command in "kill" "rm"; do - (docker network inspect "${net}" \ - --format '{{range $key, $value := .Containers}}{{$key}} {{end}}' \ - | xargs -r docker "${docker_command}") || \ - cleanup_success=0 - done - # Remove the network. - docker network rm "${net}" || \ - cleanup_success=0 - done - - if ((!$cleanup_success)); then - echo "FAIL: Cleanup command failed" - exit 4 - fi -} -trap finish EXIT - -# Subnet for control packets between test bench and DUT. -declare CTRL_NET_PREFIX=$(new_net_prefix) -while ! docker network create \ - "--subnet=${CTRL_NET_PREFIX}.0/${NET_MASK}" "${CTRL_NET}"; do - sleep 0.1 - declare CTRL_NET_PREFIX=$(new_net_prefix) -done - -# Subnet for the packets that are part of the test. -declare TEST_NET_PREFIX=$(new_net_prefix) -while ! docker network create \ - "--subnet=${TEST_NET_PREFIX}.0/${NET_MASK}" "${TEST_NET}"; do - sleep 0.1 - declare TEST_NET_PREFIX=$(new_net_prefix) -done - -docker pull "${IMAGE_TAG}" - -# Create the DUT container and connect to network. -DUT=$(docker create ${RUNTIME_ARG} --privileged --rm \ - --stop-timeout ${TIMEOUT} -it ${IMAGE_TAG}) -docker network connect "${CTRL_NET}" \ - --ip "${CTRL_NET_PREFIX}${DUT_NET_SUFFIX}" "${DUT}" \ - || (docker kill ${DUT}; docker rm ${DUT}; false) -docker network connect "${TEST_NET}" \ - --ip "${TEST_NET_PREFIX}${DUT_NET_SUFFIX}" "${DUT}" \ - || (docker kill ${DUT}; docker rm ${DUT}; false) -docker start "${DUT}" - -# Create the test bench container and connect to network. -TESTBENCH=$(docker create --privileged --rm \ - --stop-timeout ${TIMEOUT} -it ${IMAGE_TAG}) -docker network connect "${CTRL_NET}" \ - --ip "${CTRL_NET_PREFIX}${TESTBENCH_NET_SUFFIX}" "${TESTBENCH}" \ - || (docker kill ${TESTBENCH}; docker rm ${TESTBENCH}; false) -docker network connect "${TEST_NET}" \ - --ip "${TEST_NET_PREFIX}${TESTBENCH_NET_SUFFIX}" "${TESTBENCH}" \ - || (docker kill ${TESTBENCH}; docker rm ${TESTBENCH}; false) -docker start "${TESTBENCH}" - -# Start the posix_server in the DUT. -declare -r DOCKER_POSIX_SERVER_BINARY="/$(basename ${POSIX_SERVER_BINARY})" -docker cp -L ${POSIX_SERVER_BINARY} "${DUT}:${DOCKER_POSIX_SERVER_BINARY}" - -docker exec -t "${DUT}" \ - /bin/bash -c "${DOCKER_POSIX_SERVER_BINARY} \ - --ip ${CTRL_NET_PREFIX}${DUT_NET_SUFFIX} \ - --port ${CTRL_PORT}" & - -# Because the Linux kernel receives the SYN-ACK but didn't send the SYN it will -# issue a RST. To prevent this IPtables can be used to filter those out. -docker exec "${TESTBENCH}" \ - iptables -A INPUT -i ${TEST_DEVICE} -j DROP - -# Wait for the DUT server to come up. Attempt to connect to it from the test -# bench every 100 milliseconds until success. -while ! docker exec "${TESTBENCH}" \ - nc -zv "${CTRL_NET_PREFIX}${DUT_NET_SUFFIX}" "${CTRL_PORT}"; do - sleep 0.1 -done - -declare -r REMOTE_MAC=$(docker exec -t "${DUT}" ip link show \ - "${TEST_DEVICE}" | tail -1 | cut -d' ' -f6) -declare -r LOCAL_MAC=$(docker exec -t "${TESTBENCH}" ip link show \ - "${TEST_DEVICE}" | tail -1 | cut -d' ' -f6) - -declare -r DOCKER_TESTBENCH_BINARY="/$(basename ${TESTBENCH_BINARY})" -docker cp -L "${TESTBENCH_BINARY}" "${TESTBENCH}:${DOCKER_TESTBENCH_BINARY}" - -if [[ -z "${TSHARK-}" ]]; then - # Run tcpdump in the test bench unbuffered, without dns resolution, just on - # the interface with the test packets. - docker exec -t "${TESTBENCH}" \ - tcpdump -S -vvv -U -n -i "${TEST_DEVICE}" net "${TEST_NET_PREFIX}/24" & -else - # Run tshark in the test bench unbuffered, without dns resolution, just on the - # interface with the test packets. - docker exec -t "${TESTBENCH}" \ - tshark -V -l -n -i "${TEST_DEVICE}" \ - -o tcp.check_checksum:TRUE \ - -o udp.check_checksum:TRUE \ - host "${TEST_NET_PREFIX}${TESTBENCH_NET_SUFFIX}" & -fi - -# tcpdump and tshark take time to startup -sleep 3 - -# Start a packetimpact test on the test bench. The packetimpact test sends and -# receives packets and also sends POSIX socket commands to the posix_server to -# be executed on the DUT. -docker exec -t "${TESTBENCH}" \ - /bin/bash -c "${DOCKER_TESTBENCH_BINARY} \ - ${EXTRA_TEST_ARGS[@]-} \ - --posix_server_ip=${CTRL_NET_PREFIX}${DUT_NET_SUFFIX} \ - --posix_server_port=${CTRL_PORT} \ - --remote_ipv4=${TEST_NET_PREFIX}${DUT_NET_SUFFIX} \ - --local_ipv4=${TEST_NET_PREFIX}${TESTBENCH_NET_SUFFIX} \ - --remote_mac=${REMOTE_MAC} \ - --local_mac=${LOCAL_MAC} \ - --device=${TEST_DEVICE}" - -echo PASS: No errors. diff --git a/test/packetimpact/tests/udp_recv_multicast_test.go b/test/packetimpact/tests/udp_recv_multicast_test.go deleted file mode 100644 index bc1b0be49..000000000 --- a/test/packetimpact/tests/udp_recv_multicast_test.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package udp_recv_multicast_test - -import ( - "net" - "testing" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip" - tb "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func TestUDPRecvMulticast(t *testing.T) { - dut := tb.NewDUT(t) - defer dut.TearDown() - boundFD, remotePort := dut.CreateBoundSocket(unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.ParseIP("0.0.0.0")) - defer dut.Close(boundFD) - conn := tb.NewUDPIPv4(t, tb.UDP{DstPort: &remotePort}, tb.UDP{SrcPort: &remotePort}) - defer conn.Close() - frame := conn.CreateFrame(tb.UDP{}, &tb.Payload{Bytes: []byte("hello world")}) - frame[1].(*tb.IPv4).DstAddr = tb.Address(tcpip.Address(net.ParseIP("224.0.0.1").To4())) - conn.SendFrame(frame) - dut.Recv(boundFD, 100, 0) -} |