mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Packetimpact in Go with c++ stub
PiperOrigin-RevId: 301382690
This commit is contained in:
+1
-1
@@ -2,7 +2,7 @@ FROM fedora:31
|
||||
|
||||
RUN dnf install -y dnf-plugins-core && dnf copr enable -y vbatts/bazel
|
||||
|
||||
RUN dnf install -y bazel2 git gcc make golang gcc-c++ glibc-devel python3 which python3-pip python3-devel libffi-devel openssl-devel pkg-config glibc-static
|
||||
RUN dnf install -y bazel2 git gcc make golang gcc-c++ glibc-devel python3 which python3-pip python3-devel libffi-devel openssl-devel pkg-config glibc-static libstdc++-static patch
|
||||
|
||||
RUN pip install pycparser
|
||||
|
||||
|
||||
@@ -161,6 +161,20 @@ load(
|
||||
|
||||
_go_image_repos()
|
||||
|
||||
# Load C++ grpc rules.
|
||||
http_archive(
|
||||
name = "com_github_grpc_grpc",
|
||||
sha256 = "2fcb7f1ab160d6fd3aaade64520be3e5446fc4c6fa7ba6581afdc4e26094bd81",
|
||||
strip_prefix = "grpc-1.26.0",
|
||||
urls = [
|
||||
"https://github.com/grpc/grpc/archive/v1.26.0.tar.gz",
|
||||
],
|
||||
)
|
||||
load("@com_github_grpc_grpc//bazel:grpc_deps.bzl", "grpc_deps")
|
||||
grpc_deps()
|
||||
load("@com_github_grpc_grpc//bazel:grpc_extra_deps.bzl", "grpc_extra_deps")
|
||||
grpc_extra_deps()
|
||||
|
||||
# External repositories, in sorted order.
|
||||
go_repository(
|
||||
name = "com_github_cenkalti_backoff",
|
||||
@@ -204,6 +218,13 @@ go_repository(
|
||||
version = "v0.0.0-20171129191014-dec09d789f3d",
|
||||
)
|
||||
|
||||
go_repository(
|
||||
name = "com_github_imdario_mergo",
|
||||
importpath = "github.com/imdario/mergo",
|
||||
version = "v0.3.8",
|
||||
sum = "h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=",
|
||||
)
|
||||
|
||||
go_repository(
|
||||
name = "com_github_kr_pretty",
|
||||
importpath = "github.com/kr/pretty",
|
||||
@@ -225,6 +246,12 @@ go_repository(
|
||||
version = "v0.1.0",
|
||||
)
|
||||
|
||||
go_repository(
|
||||
name = "com_github_mohae_deepcopy",
|
||||
importpath = "github.com/mohae/deepcopy",
|
||||
commit = "c48cc78d482608239f6c4c92a4abd87eb8761c90",
|
||||
)
|
||||
|
||||
go_repository(
|
||||
name = "com_github_opencontainers_runtime-spec",
|
||||
importpath = "github.com/opencontainers/runtime-spec",
|
||||
@@ -253,6 +280,14 @@ go_repository(
|
||||
version = "v0.0.0-20171111001504-be1fbeda1936",
|
||||
)
|
||||
|
||||
go_repository(
|
||||
name = "org_golang_google_grpc",
|
||||
build_file_proto_mode = "disable",
|
||||
importpath = "google.golang.org/grpc",
|
||||
sum = "h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=",
|
||||
version = "v1.27.1",
|
||||
)
|
||||
|
||||
go_repository(
|
||||
name = "in_gopkg_check_v1",
|
||||
importpath = "gopkg.in/check.v1",
|
||||
|
||||
+40
-2
@@ -81,7 +81,8 @@ type TCPFields struct {
|
||||
// AckNum is the "acknowledgement number" field of a TCP packet.
|
||||
AckNum uint32
|
||||
|
||||
// DataOffset is the "data offset" field of a TCP packet.
|
||||
// DataOffset is the "data offset" field of a TCP packet. It is the length of
|
||||
// the TCP header in bytes.
|
||||
DataOffset uint8
|
||||
|
||||
// Flags is the "flags" field of a TCP packet.
|
||||
@@ -213,7 +214,8 @@ func (b TCP) AckNumber() uint32 {
|
||||
return binary.BigEndian.Uint32(b[TCPAckNumOffset:])
|
||||
}
|
||||
|
||||
// DataOffset returns the "data offset" field of the tcp header.
|
||||
// DataOffset returns the "data offset" field of the tcp header. The return
|
||||
// value is the length of the TCP header in bytes.
|
||||
func (b TCP) DataOffset() uint8 {
|
||||
return (b[TCPDataOffset] >> 4) * 4
|
||||
}
|
||||
@@ -238,6 +240,11 @@ func (b TCP) Checksum() uint16 {
|
||||
return binary.BigEndian.Uint16(b[TCPChecksumOffset:])
|
||||
}
|
||||
|
||||
// UrgentPointer returns the "urgent pointer" field of the tcp header.
|
||||
func (b TCP) UrgentPointer() uint16 {
|
||||
return binary.BigEndian.Uint16(b[TCPUrgentPtrOffset:])
|
||||
}
|
||||
|
||||
// SetSourcePort sets the "source port" field of the tcp header.
|
||||
func (b TCP) SetSourcePort(port uint16) {
|
||||
binary.BigEndian.PutUint16(b[TCPSrcPortOffset:], port)
|
||||
@@ -253,6 +260,37 @@ func (b TCP) SetChecksum(checksum uint16) {
|
||||
binary.BigEndian.PutUint16(b[TCPChecksumOffset:], checksum)
|
||||
}
|
||||
|
||||
// SetDataOffset sets the data offset field of the tcp header. headerLen should
|
||||
// be the length of the TCP header in bytes.
|
||||
func (b TCP) SetDataOffset(headerLen uint8) {
|
||||
b[TCPDataOffset] = (headerLen / 4) << 4
|
||||
}
|
||||
|
||||
// SetSequenceNumber sets the sequence number field of the tcp header.
|
||||
func (b TCP) SetSequenceNumber(seqNum uint32) {
|
||||
binary.BigEndian.PutUint32(b[TCPSeqNumOffset:], seqNum)
|
||||
}
|
||||
|
||||
// SetAckNumber sets the ack number field of the tcp header.
|
||||
func (b TCP) SetAckNumber(ackNum uint32) {
|
||||
binary.BigEndian.PutUint32(b[TCPAckNumOffset:], ackNum)
|
||||
}
|
||||
|
||||
// SetFlags sets the flags field of the tcp header.
|
||||
func (b TCP) SetFlags(flags uint8) {
|
||||
b[TCPFlagsOffset] = flags
|
||||
}
|
||||
|
||||
// SetWindowSize sets the window size field of the tcp header.
|
||||
func (b TCP) SetWindowSize(rcvwnd uint16) {
|
||||
binary.BigEndian.PutUint16(b[TCPWinSizeOffset:], rcvwnd)
|
||||
}
|
||||
|
||||
// SetUrgentPoiner sets the window size field of the tcp header.
|
||||
func (b TCP) SetUrgentPoiner(urgentPointer uint16) {
|
||||
binary.BigEndian.PutUint16(b[TCPUrgentPtrOffset:], urgentPointer)
|
||||
}
|
||||
|
||||
// CalculateChecksum calculates the checksum of the tcp segment.
|
||||
// partialChecksum is the checksum of the network-layer pseudo-header
|
||||
// and the checksum of the segment data.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
FROM ubuntu:bionic
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y net-tools git iptables iputils-ping netcat tcpdump jq tar
|
||||
RUN apt-get update && apt-get install -y net-tools git iptables iputils-ping \
|
||||
netcat tcpdump jq tar bison flex make
|
||||
RUN hash -r
|
||||
RUN git clone --branch packetdrill-v2.0 \
|
||||
https://github.com/google/packetdrill.git
|
||||
RUN cd packetdrill/gtests/net/packetdrill && ./configure && \
|
||||
apt-get install -y bison flex make && make
|
||||
RUN cd packetdrill/gtests/net/packetdrill && ./configure && make
|
||||
CMD /bin/bash
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
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",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,229 @@
|
||||
// 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 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 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 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 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 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 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 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;
|
||||
}
|
||||
};
|
||||
|
||||
// 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);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
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,
|
||||
)
|
||||
@@ -0,0 +1,150 @@
|
||||
// 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 SocketRequest {
|
||||
int32 domain = 1;
|
||||
int32 type = 2;
|
||||
int32 protocol = 3;
|
||||
}
|
||||
|
||||
message SocketResponse {
|
||||
int32 fd = 1;
|
||||
int32 errno_ = 2;
|
||||
}
|
||||
|
||||
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 BindRequest {
|
||||
int32 sockfd = 1;
|
||||
Sockaddr addr = 2;
|
||||
}
|
||||
|
||||
message BindResponse {
|
||||
int32 ret = 1;
|
||||
int32 errno_ = 2;
|
||||
}
|
||||
|
||||
message GetSockNameRequest {
|
||||
int32 sockfd = 1;
|
||||
}
|
||||
|
||||
message GetSockNameResponse {
|
||||
int32 ret = 1;
|
||||
int32 errno_ = 2;
|
||||
Sockaddr addr = 3;
|
||||
}
|
||||
|
||||
message ListenRequest {
|
||||
int32 sockfd = 1;
|
||||
int32 backlog = 2;
|
||||
}
|
||||
|
||||
message ListenResponse {
|
||||
int32 ret = 1;
|
||||
int32 errno_ = 2;
|
||||
}
|
||||
|
||||
message AcceptRequest {
|
||||
int32 sockfd = 1;
|
||||
}
|
||||
|
||||
message AcceptResponse {
|
||||
int32 fd = 1;
|
||||
int32 errno_ = 2;
|
||||
Sockaddr addr = 3;
|
||||
}
|
||||
|
||||
message SetSockOptRequest {
|
||||
int32 sockfd = 1;
|
||||
int32 level = 2;
|
||||
int32 optname = 3;
|
||||
bytes optval = 4;
|
||||
}
|
||||
|
||||
message SetSockOptResponse {
|
||||
int32 ret = 1;
|
||||
int32 errno_ = 2;
|
||||
}
|
||||
|
||||
message Timeval {
|
||||
int64 seconds = 1;
|
||||
int64 microseconds = 2;
|
||||
}
|
||||
|
||||
message SetSockOptTimevalRequest {
|
||||
int32 sockfd = 1;
|
||||
int32 level = 2;
|
||||
int32 optname = 3;
|
||||
Timeval timeval = 4;
|
||||
}
|
||||
|
||||
message SetSockOptTimevalResponse {
|
||||
int32 ret = 1;
|
||||
int32 errno_ = 2;
|
||||
}
|
||||
|
||||
message CloseRequest {
|
||||
int32 fd = 1;
|
||||
}
|
||||
|
||||
message CloseResponse {
|
||||
int32 ret = 1;
|
||||
int32 errno_ = 2;
|
||||
}
|
||||
|
||||
service Posix {
|
||||
// Call socket() on the DUT.
|
||||
rpc Socket(SocketRequest) returns (SocketResponse);
|
||||
// Call bind() on the DUT.
|
||||
rpc Bind(BindRequest) returns (BindResponse);
|
||||
// Call getsockname() on the DUT.
|
||||
rpc GetSockName(GetSockNameRequest) returns (GetSockNameResponse);
|
||||
// Call listen() on the DUT.
|
||||
rpc Listen(ListenRequest) returns (ListenResponse);
|
||||
// Call accept() on the DUT.
|
||||
rpc Accept(AcceptRequest) returns (AcceptResponse);
|
||||
// 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 a Timeval optval.
|
||||
rpc SetSockOptTimeval(SetSockOptTimevalRequest)
|
||||
returns (SetSockOptTimevalResponse);
|
||||
// Call close() on the DUT.
|
||||
rpc Close(CloseRequest) returns (CloseResponse);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
load("//tools:defs.bzl", "go_library")
|
||||
|
||||
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/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",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,245 @@
|
||||
// Copyright 2020 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package testbench has utilities to send and receive packets and also command
|
||||
// the DUT to run POSIX functions.
|
||||
package testbench
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mohae/deepcopy"
|
||||
"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")
|
||||
|
||||
// TCPIPv4 maintains state about a TCP/IPv4 connection.
|
||||
type TCPIPv4 struct {
|
||||
outgoing Layers
|
||||
incoming Layers
|
||||
LocalSeqNum seqnum.Value
|
||||
RemoteSeqNum seqnum.Value
|
||||
SynAck *TCP
|
||||
sniffer Sniffer
|
||||
injector Injector
|
||||
portPickerFD int
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
// pickPort makes a new socket and returns the socket FD and port. The caller
|
||||
// must close the FD when done with the port if there is no error.
|
||||
func pickPort() (int, uint16, error) {
|
||||
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
|
||||
}
|
||||
|
||||
// tcpLayerIndex is the position of the TCP layer in the TCPIPv4 connection. It
|
||||
// is the third, after Ethernet and IPv4.
|
||||
const tcpLayerIndex int = 2
|
||||
|
||||
// NewTCPIPv4 creates a new TCPIPv4 connection with reasonable defaults.
|
||||
func NewTCPIPv4(t *testing.T, dut DUT, outgoingTCP, incomingTCP TCP) TCPIPv4 {
|
||||
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{
|
||||
DataOffset: Uint8(header.TCPMinimumSize),
|
||||
WindowSize: Uint16(32768),
|
||||
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
|
||||
}
|
||||
|
||||
// Send a packet with reasonable defaults and override some fields by tcp.
|
||||
func (conn *TCPIPv4) Send(tcp TCP, additionalLayers ...Layer) {
|
||||
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...)
|
||||
outBytes, err := layersToSend.toBytes()
|
||||
if err != nil {
|
||||
conn.t.Fatalf("can't build outgoing TCP packet: %s", err)
|
||||
}
|
||||
conn.injector.Send(outBytes)
|
||||
|
||||
// Compute the next TCP sequence number.
|
||||
for i := tcpLayerIndex + 1; i < len(layersToSend); i++ {
|
||||
conn.LocalSeqNum.UpdateForward(seqnum.Size(layersToSend[i].length()))
|
||||
}
|
||||
if tcp.Flags != nil && *tcp.Flags&(header.TCPFlagSyn|header.TCPFlagFin) != 0 {
|
||||
conn.LocalSeqNum.UpdateForward(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Recv gets a packet from the sniffer within the timeout provided. If no packet
|
||||
// arrives before the timeout, it returns nil.
|
||||
func (conn *TCPIPv4) Recv(timeout time.Duration) *TCP {
|
||||
deadline := time.Now().Add(timeout)
|
||||
for {
|
||||
timeout = deadline.Sub(time.Now())
|
||||
if timeout <= 0 {
|
||||
break
|
||||
}
|
||||
b := conn.sniffer.Recv(timeout)
|
||||
if b == nil {
|
||||
break
|
||||
}
|
||||
layers, err := ParseEther(b)
|
||||
if err != nil {
|
||||
continue // Ignore packets that can't be parsed.
|
||||
}
|
||||
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 tcpHeader
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Expect a packet that matches the provided tcp within the timeout specified.
|
||||
// If it doesn't arrive in time, the test fails.
|
||||
func (conn *TCPIPv4) Expect(tcp TCP, timeout time.Duration) *TCP {
|
||||
deadline := time.Now().Add(timeout)
|
||||
for {
|
||||
timeout = deadline.Sub(time.Now())
|
||||
if timeout <= 0 {
|
||||
return nil
|
||||
}
|
||||
gotTCP := conn.Recv(timeout)
|
||||
if gotTCP == nil {
|
||||
return nil
|
||||
}
|
||||
if tcp.match(gotTCP) {
|
||||
return gotTCP
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handshake performs a TCP 3-way handshake.
|
||||
func (conn *TCPIPv4) Handshake() {
|
||||
// Send the SYN.
|
||||
conn.Send(TCP{Flags: Uint8(header.TCPFlagSyn)})
|
||||
|
||||
// Wait for the SYN-ACK.
|
||||
conn.SynAck = conn.Expect(TCP{Flags: Uint8(header.TCPFlagSyn | header.TCPFlagAck)}, time.Second)
|
||||
if conn.SynAck == nil {
|
||||
conn.t.Fatalf("didn't get synack during handshake")
|
||||
}
|
||||
|
||||
// Send an ACK.
|
||||
conn.Send(TCP{Flags: Uint8(header.TCPFlagAck)})
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
// 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()
|
||||
}
|
||||
|
||||
// SocketWithErrno calls socket on the DUT and returns the fd and errno.
|
||||
func (dut *DUT) SocketWithErrno(domain, typ, proto int32) (int32, error) {
|
||||
dut.t.Helper()
|
||||
req := pb.SocketRequest{
|
||||
Domain: domain,
|
||||
Type: typ,
|
||||
Protocol: proto,
|
||||
}
|
||||
ctx := context.Background()
|
||||
resp, err := dut.posixServer.Socket(ctx, &req)
|
||||
if err != nil {
|
||||
dut.t.Fatalf("failed to call Socket: %s", err)
|
||||
}
|
||||
return resp.GetFd(), syscall.Errno(resp.GetErrno_())
|
||||
}
|
||||
|
||||
// Socket calls socket on the DUT and returns the file descriptor. If socket
|
||||
// fails on the DUT, the test ends.
|
||||
func (dut *DUT) Socket(domain, typ, proto int32) int32 {
|
||||
dut.t.Helper()
|
||||
fd, err := dut.SocketWithErrno(domain, typ, proto)
|
||||
if fd < 0 {
|
||||
dut.t.Fatalf("failed to create socket: %s", err)
|
||||
}
|
||||
return fd
|
||||
}
|
||||
|
||||
func (dut *DUT) sockaddrToProto(sa unix.Sockaddr) *pb.Sockaddr {
|
||||
dut.t.Helper()
|
||||
switch s := sa.(type) {
|
||||
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
|
||||
}
|
||||
|
||||
// BindWithErrno calls bind on the DUT.
|
||||
func (dut *DUT) BindWithErrno(fd int32, sa unix.Sockaddr) (int32, error) {
|
||||
dut.t.Helper()
|
||||
req := pb.BindRequest{
|
||||
Sockfd: fd,
|
||||
Addr: dut.sockaddrToProto(sa),
|
||||
}
|
||||
ctx := context.Background()
|
||||
resp, err := dut.posixServer.Bind(ctx, &req)
|
||||
if err != nil {
|
||||
dut.t.Fatalf("failed to call Bind: %s", err)
|
||||
}
|
||||
return resp.GetRet(), syscall.Errno(resp.GetErrno_())
|
||||
}
|
||||
|
||||
// Bind calls bind on the DUT and causes a fatal test failure if it doesn't
|
||||
// succeed.
|
||||
func (dut *DUT) Bind(fd int32, sa unix.Sockaddr) {
|
||||
dut.t.Helper()
|
||||
ret, err := dut.BindWithErrno(fd, sa)
|
||||
if ret != 0 {
|
||||
dut.t.Fatalf("failed to bind socket: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// GetSockNameWithErrno calls getsockname on the DUT.
|
||||
func (dut *DUT) GetSockNameWithErrno(sockfd int32) (int32, unix.Sockaddr, error) {
|
||||
dut.t.Helper()
|
||||
req := pb.GetSockNameRequest{
|
||||
Sockfd: sockfd,
|
||||
}
|
||||
ctx := context.Background()
|
||||
resp, err := dut.posixServer.GetSockName(ctx, &req)
|
||||
if err != nil {
|
||||
dut.t.Fatalf("failed to call Bind: %s", err)
|
||||
}
|
||||
return resp.GetRet(), dut.protoToSockaddr(resp.GetAddr()), syscall.Errno(resp.GetErrno_())
|
||||
}
|
||||
|
||||
// GetSockName calls getsockname on the DUT and causes a fatal test failure if
|
||||
// it doens't succeed.
|
||||
func (dut *DUT) GetSockName(sockfd int32) unix.Sockaddr {
|
||||
dut.t.Helper()
|
||||
ret, sa, err := dut.GetSockNameWithErrno(sockfd)
|
||||
if ret != 0 {
|
||||
dut.t.Fatalf("failed to getsockname: %s", err)
|
||||
}
|
||||
return sa
|
||||
}
|
||||
|
||||
// ListenWithErrno calls listen on the DUT.
|
||||
func (dut *DUT) ListenWithErrno(sockfd, backlog int32) (int32, error) {
|
||||
dut.t.Helper()
|
||||
req := pb.ListenRequest{
|
||||
Sockfd: sockfd,
|
||||
Backlog: backlog,
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout)
|
||||
defer cancel()
|
||||
resp, err := dut.posixServer.Listen(ctx, &req)
|
||||
if err != nil {
|
||||
dut.t.Fatalf("failed to call Listen: %s", err)
|
||||
}
|
||||
return resp.GetRet(), syscall.Errno(resp.GetErrno_())
|
||||
}
|
||||
|
||||
// Listen calls listen on the DUT and causes a fatal test failure if it doesn't
|
||||
// succeed.
|
||||
func (dut *DUT) Listen(sockfd, backlog int32) {
|
||||
dut.t.Helper()
|
||||
ret, err := dut.ListenWithErrno(sockfd, backlog)
|
||||
if ret != 0 {
|
||||
dut.t.Fatalf("failed to listen: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// AcceptWithErrno calls accept on the DUT.
|
||||
func (dut *DUT) AcceptWithErrno(sockfd int32) (int32, unix.Sockaddr, error) {
|
||||
dut.t.Helper()
|
||||
req := pb.AcceptRequest{
|
||||
Sockfd: sockfd,
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout)
|
||||
defer cancel()
|
||||
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_())
|
||||
}
|
||||
|
||||
// Accept calls accept on the DUT and causes a fatal test failure if it doesn't
|
||||
// succeed.
|
||||
func (dut *DUT) Accept(sockfd int32) (int32, unix.Sockaddr) {
|
||||
dut.t.Helper()
|
||||
fd, sa, err := dut.AcceptWithErrno(sockfd)
|
||||
if fd < 0 {
|
||||
dut.t.Fatalf("failed to accept: %s", err)
|
||||
}
|
||||
return fd, sa
|
||||
}
|
||||
|
||||
// SetSockOptWithErrno calls setsockopt on the DUT.
|
||||
func (dut *DUT) SetSockOptWithErrno(sockfd, level, optname int32, optval []byte) (int32, error) {
|
||||
dut.t.Helper()
|
||||
req := pb.SetSockOptRequest{
|
||||
Sockfd: sockfd,
|
||||
Level: level,
|
||||
Optname: optname,
|
||||
Optval: optval,
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout)
|
||||
defer cancel()
|
||||
resp, err := dut.posixServer.SetSockOpt(ctx, &req)
|
||||
if err != nil {
|
||||
dut.t.Fatalf("failed to call SetSockOpt: %s", err)
|
||||
}
|
||||
return resp.GetRet(), syscall.Errno(resp.GetErrno_())
|
||||
}
|
||||
|
||||
// SetSockOpt calls setsockopt on the DUT and causes a fatal test failure if it
|
||||
// doesn't succeed.
|
||||
func (dut *DUT) SetSockOpt(sockfd, level, optname int32, optval []byte) {
|
||||
dut.t.Helper()
|
||||
ret, err := dut.SetSockOptWithErrno(sockfd, level, optname, optval)
|
||||
if ret != 0 {
|
||||
dut.t.Fatalf("failed to SetSockOpt: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// SetSockOptTimevalWithErrno calls setsockopt with the timeval converted to
|
||||
// bytes.
|
||||
func (dut *DUT) SetSockOptTimevalWithErrno(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,
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout)
|
||||
defer cancel()
|
||||
resp, err := dut.posixServer.SetSockOptTimeval(ctx, &req)
|
||||
if err != nil {
|
||||
dut.t.Fatalf("failed to call SetSockOptTimeval: %s", err)
|
||||
}
|
||||
return resp.GetRet(), syscall.Errno(resp.GetErrno_())
|
||||
}
|
||||
|
||||
// SetSockOptTimeval calls setsockopt on the DUT and causes a fatal test failure
|
||||
// if it doesn't succeed.
|
||||
func (dut *DUT) SetSockOptTimeval(sockfd, level, optname int32, tv *unix.Timeval) {
|
||||
dut.t.Helper()
|
||||
ret, err := dut.SetSockOptTimevalWithErrno(sockfd, level, optname, tv)
|
||||
if ret != 0 {
|
||||
dut.t.Fatalf("failed to SetSockOptTimeval: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// CloseWithErrno calls close on the DUT.
|
||||
func (dut *DUT) CloseWithErrno(fd int32) (int32, error) {
|
||||
dut.t.Helper()
|
||||
req := pb.CloseRequest{
|
||||
Fd: fd,
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout)
|
||||
defer cancel()
|
||||
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_())
|
||||
}
|
||||
|
||||
// Close calls close on the DUT and causes a fatal test failure if it doesn't
|
||||
// succeed.
|
||||
func (dut *DUT) Close(fd int32) {
|
||||
dut.t.Helper()
|
||||
ret, err := dut.CloseWithErrno(fd)
|
||||
if ret != 0 {
|
||||
dut.t.Fatalf("failed to close: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// CreateListener makes a new TCP connection. If it fails, the test ends.
|
||||
func (dut *DUT) CreateListener(typ, proto, backlog int32) (int32, uint16) {
|
||||
dut.t.Helper()
|
||||
addr := net.ParseIP(*remoteIPv4)
|
||||
var fd int32
|
||||
if addr.To4() != nil {
|
||||
fd = dut.Socket(unix.AF_INET, typ, proto)
|
||||
sa := unix.SockaddrInet4{}
|
||||
copy(sa.Addr[:], addr.To4())
|
||||
dut.Bind(fd, &sa)
|
||||
} else if addr.To16() != nil {
|
||||
fd = dut.Socket(unix.AF_INET6, typ, proto)
|
||||
sa := unix.SockaddrInet6{}
|
||||
copy(sa.Addr[:], addr.To16())
|
||||
dut.Bind(fd, &sa)
|
||||
} else {
|
||||
dut.t.Fatal("unknown ip addr type for remoteIP")
|
||||
}
|
||||
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)
|
||||
}
|
||||
dut.Listen(fd, backlog)
|
||||
return fd, uint16(port)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// 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)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,151 @@
|
||||
// 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
|
||||
}
|
||||
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]
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
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",
|
||||
],
|
||||
)
|
||||
|
||||
sh_binary(
|
||||
name = "test_runner",
|
||||
srcs = ["test_runner.sh"],
|
||||
)
|
||||
@@ -0,0 +1,5 @@
|
||||
FROM ubuntu:bionic
|
||||
|
||||
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y iptables netcat tcpdump iproute2 tshark
|
||||
RUN hash -r
|
||||
CMD /bin/bash
|
||||
@@ -0,0 +1,106 @@
|
||||
"""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,
|
||||
**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,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def packetimpact_go_test(name, size = "small", pure = True, **kwargs):
|
||||
testbench_binary = name + "_test"
|
||||
go_test(
|
||||
name = testbench_binary,
|
||||
size = size,
|
||||
pure = pure,
|
||||
tags = PACKETIMPACT_TAGS,
|
||||
**kwargs
|
||||
)
|
||||
packetimpact_linux_test(name = name, testbench_binary = testbench_binary)
|
||||
packetimpact_netstack_test(name = name, testbench_binary = testbench_binary)
|
||||
@@ -0,0 +1,68 @@
|
||||
// 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, dut, 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 gotOne := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); gotOne == nil {
|
||||
t.Fatal("expected a FIN-ACK within 1 second but got none")
|
||||
}
|
||||
conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)})
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)})
|
||||
if tt.linger2 {
|
||||
if gotOne := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagRst)}, time.Second); gotOne == nil {
|
||||
t.Fatal("expected a RST packet within a second but got none")
|
||||
}
|
||||
} else {
|
||||
if gotOne := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagRst)}, 10*time.Second); gotOne != nil {
|
||||
t.Fatal("expected no RST packets within ten seconds but got one")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Executable
+246
@@ -0,0 +1,246 @@
|
||||
#!/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"
|
||||
|
||||
# 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"
|
||||
|
||||
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
|
||||
;;
|
||||
--)
|
||||
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
|
||||
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}" \
|
||||
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} \
|
||||
--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.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user