// Copyright 2020 The gVisor Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package testbench import ( "context" "encoding/binary" "fmt" "net" "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" ) // DUT communicates with the DUT to force it to make POSIX calls. type DUT struct { conn *grpc.ClientConn posixServer POSIXClient Net *DUTTestNet } // NewDUT creates a new connection with the DUT over gRPC. func NewDUT(t *testing.T) DUT { t.Helper() n := GetDUTTestNet() dut := n.ConnectToDUT(t) t.Cleanup(func() { dut.TearDownConnection() dut.Net.Release() }) return dut } // ConnectToDUT connects to DUT through gRPC. func (n *DUTTestNet) ConnectToDUT(t *testing.T) DUT { t.Helper() posixServerAddress := net.JoinHostPort(n.POSIXServerIP.String(), fmt.Sprintf("%d", n.POSIXServerPort)) conn, err := grpc.Dial(posixServerAddress, grpc.WithInsecure(), grpc.WithKeepaliveParams(keepalive.ClientParameters{Timeout: RPCKeepalive})) if err != nil { t.Fatalf("failed to grpc.Dial(%s): %s", posixServerAddress, err) } posixServer := NewPOSIXClient(conn) return DUT{ conn: conn, posixServer: posixServer, Net: n, } } // TearDownConnection closes the underlying connection. func (dut *DUT) TearDownConnection() { dut.conn.Close() } func (dut *DUT) sockaddrToProto(t *testing.T, sa unix.Sockaddr) *pb.Sockaddr { t.Helper() switch s := sa.(type) { case *unix.SockaddrInet4: return &pb.Sockaddr{ Sockaddr: &pb.Sockaddr_In{ In: &pb.SockaddrIn{ Family: unix.AF_INET, Port: uint32(s.Port), Addr: s.Addr[:], }, }, } case *unix.SockaddrInet6: return &pb.Sockaddr{ Sockaddr: &pb.Sockaddr_In6{ In6: &pb.SockaddrIn6{ Family: unix.AF_INET6, Port: uint32(s.Port), Flowinfo: 0, ScopeId: s.ZoneId, Addr: s.Addr[:], }, }, } } t.Fatalf("can't parse Sockaddr struct: %+v", sa) return nil } func (dut *DUT) protoToSockaddr(t *testing.T, sa *pb.Sockaddr) unix.Sockaddr { t.Helper() switch s := sa.Sockaddr.(type) { case *pb.Sockaddr_In: ret := unix.SockaddrInet4{ Port: int(s.In.GetPort()), } copy(ret.Addr[:], s.In.GetAddr()) return &ret case *pb.Sockaddr_In6: ret := unix.SockaddrInet6{ Port: int(s.In6.GetPort()), ZoneId: s.In6.GetScopeId(), } copy(ret.Addr[:], s.In6.GetAddr()) return &ret } t.Fatalf("can't parse Sockaddr proto: %#v", sa) return nil } // CreateBoundSocket makes a new socket on the DUT, with type typ and protocol // proto, and bound to the IP address addr. Returns the new file descriptor and // the port that was selected on the DUT. func (dut *DUT) CreateBoundSocket(t *testing.T, typ, proto int32, addr net.IP) (int32, uint16) { t.Helper() var fd int32 if addr.To4() != nil { fd = dut.Socket(t, unix.AF_INET, typ, proto) sa := unix.SockaddrInet4{} copy(sa.Addr[:], addr.To4()) dut.Bind(t, fd, &sa) } else if addr.To16() != nil { fd = dut.Socket(t, unix.AF_INET6, typ, proto) sa := unix.SockaddrInet6{} copy(sa.Addr[:], addr.To16()) sa.ZoneId = dut.Net.RemoteDevID dut.Bind(t, fd, &sa) } else { t.Fatalf("invalid IP address: %s", addr) } sa := dut.GetSockName(t, fd) var port int switch s := sa.(type) { case *unix.SockaddrInet4: port = s.Port case *unix.SockaddrInet6: port = s.Port default: t.Fatalf("unknown sockaddr type from getsockname: %T", sa) } return fd, uint16(port) } // CreateListener makes a new TCP connection. If it fails, the test ends. func (dut *DUT) CreateListener(t *testing.T, typ, proto, backlog int32) (int32, uint16) { t.Helper() fd, remotePort := dut.CreateBoundSocket(t, typ, proto, dut.Net.RemoteIPv4) dut.Listen(t, fd, backlog) return fd, remotePort } // All the functions that make gRPC calls to the POSIX service are below, sorted // alphabetically. // Accept calls accept on the DUT and causes a fatal test failure if it doesn't // succeed. If more control over the timeout or error handling is needed, use // AcceptWithErrno. func (dut *DUT) Accept(t *testing.T, sockfd int32) (int32, unix.Sockaddr) { t.Helper() ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) defer cancel() fd, sa, err := dut.AcceptWithErrno(ctx, t, sockfd) if fd < 0 { t.Fatalf("failed to accept: %s", err) } return fd, sa } // AcceptWithErrno calls accept on the DUT. func (dut *DUT) AcceptWithErrno(ctx context.Context, t *testing.T, sockfd int32) (int32, unix.Sockaddr, error) { t.Helper() req := pb.AcceptRequest{ Sockfd: sockfd, } resp, err := dut.posixServer.Accept(ctx, &req) if err != nil { t.Fatalf("failed to call Accept: %s", err) } return resp.GetFd(), dut.protoToSockaddr(t, resp.GetAddr()), 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(t *testing.T, fd int32, sa unix.Sockaddr) { t.Helper() ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) defer cancel() ret, err := dut.BindWithErrno(ctx, t, fd, sa) if ret != 0 { t.Fatalf("failed to bind socket: %s", err) } } // BindWithErrno calls bind on the DUT. func (dut *DUT) BindWithErrno(ctx context.Context, t *testing.T, fd int32, sa unix.Sockaddr) (int32, error) { t.Helper() req := pb.BindRequest{ Sockfd: fd, Addr: dut.sockaddrToProto(t, sa), } resp, err := dut.posixServer.Bind(ctx, &req) if err != nil { t.Fatalf("failed to call Bind: %s", err) } return resp.GetRet(), 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(t *testing.T, fd int32) { t.Helper() ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) defer cancel() ret, err := dut.CloseWithErrno(ctx, t, fd) if ret != 0 { t.Fatalf("failed to close: %s", err) } } // CloseWithErrno calls close on the DUT. func (dut *DUT) CloseWithErrno(ctx context.Context, t *testing.T, fd int32) (int32, error) { t.Helper() req := pb.CloseRequest{ Fd: fd, } resp, err := dut.posixServer.Close(ctx, &req) if err != nil { t.Fatalf("failed to call Close: %s", err) } return resp.GetRet(), syscall.Errno(resp.GetErrno_()) } // Connect calls connect on the DUT and causes a fatal test failure if it // doesn't succeed. If more control over the timeout or error handling is // needed, use ConnectWithErrno. func (dut *DUT) Connect(t *testing.T, fd int32, sa unix.Sockaddr) { t.Helper() ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) defer cancel() ret, err := dut.ConnectWithErrno(ctx, t, fd, sa) // Ignore 'operation in progress' error that can be returned when the socket // is non-blocking. if err != syscall.Errno(unix.EINPROGRESS) && ret != 0 { t.Fatalf("failed to connect socket: %s", err) } } // ConnectWithErrno calls bind on the DUT. func (dut *DUT) ConnectWithErrno(ctx context.Context, t *testing.T, fd int32, sa unix.Sockaddr) (int32, error) { t.Helper() req := pb.ConnectRequest{ Sockfd: fd, Addr: dut.sockaddrToProto(t, sa), } resp, err := dut.posixServer.Connect(ctx, &req) if err != nil { t.Fatalf("failed to call Connect: %s", err) } return resp.GetRet(), syscall.Errno(resp.GetErrno_()) } // Fcntl calls fcntl 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 FcntlWithErrno. func (dut *DUT) Fcntl(t *testing.T, fd, cmd, arg int32) int32 { t.Helper() ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) defer cancel() ret, err := dut.FcntlWithErrno(ctx, t, fd, cmd, arg) if ret == -1 { t.Fatalf("failed to Fcntl: ret=%d, errno=%s", ret, err) } return ret } // FcntlWithErrno calls fcntl on the DUT. func (dut *DUT) FcntlWithErrno(ctx context.Context, t *testing.T, fd, cmd, arg int32) (int32, error) { t.Helper() req := pb.FcntlRequest{ Fd: fd, Cmd: cmd, Arg: arg, } resp, err := dut.posixServer.Fcntl(ctx, &req) if err != nil { t.Fatalf("failed to call Fcntl: %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(t *testing.T, sockfd int32) unix.Sockaddr { t.Helper() ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) defer cancel() ret, sa, err := dut.GetSockNameWithErrno(ctx, t, sockfd) if ret != 0 { t.Fatalf("failed to getsockname: %s", err) } return sa } // GetSockNameWithErrno calls getsockname on the DUT. func (dut *DUT) GetSockNameWithErrno(ctx context.Context, t *testing.T, sockfd int32) (int32, unix.Sockaddr, error) { t.Helper() req := pb.GetSockNameRequest{ Sockfd: sockfd, } resp, err := dut.posixServer.GetSockName(ctx, &req) if err != nil { t.Fatalf("failed to call Bind: %s", err) } return resp.GetRet(), dut.protoToSockaddr(t, resp.GetAddr()), syscall.Errno(resp.GetErrno_()) } func (dut *DUT) getSockOpt(ctx context.Context, t *testing.T, sockfd, level, optname, optlen int32, typ pb.GetSockOptRequest_SockOptType) (int32, *pb.SockOptVal, error) { t.Helper() req := pb.GetSockOptRequest{ Sockfd: sockfd, Level: level, Optname: optname, Optlen: optlen, Type: typ, } resp, err := dut.posixServer.GetSockOpt(ctx, &req) if err != nil { t.Fatalf("failed to call GetSockOpt: %s", err) } optval := resp.GetOptval() if optval == nil { t.Fatalf("GetSockOpt response does not contain a value") } return resp.GetRet(), optval, syscall.Errno(resp.GetErrno_()) } // GetSockOpt calls getsockopt on the DUT and causes a fatal test failure if it // doesn't succeed. If more control over the timeout or error handling is // needed, use GetSockOptWithErrno. Because endianess and the width of values // might differ between the testbench and DUT architectures, prefer to use a // more specific GetSockOptXxx function. func (dut *DUT) GetSockOpt(t *testing.T, sockfd, level, optname, optlen int32) []byte { t.Helper() ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) defer cancel() ret, optval, err := dut.GetSockOptWithErrno(ctx, t, sockfd, level, optname, optlen) if ret != 0 { t.Fatalf("failed to GetSockOpt: %s", err) } return optval } // GetSockOptWithErrno calls getsockopt on the DUT. Because endianess and the // width of values might differ between the testbench and DUT architectures, // prefer to use a more specific GetSockOptXxxWithErrno function. func (dut *DUT) GetSockOptWithErrno(ctx context.Context, t *testing.T, sockfd, level, optname, optlen int32) (int32, []byte, error) { t.Helper() ret, optval, errno := dut.getSockOpt(ctx, t, sockfd, level, optname, optlen, pb.GetSockOptRequest_BYTES) bytesval, ok := optval.Val.(*pb.SockOptVal_Bytesval) if !ok { t.Fatalf("GetSockOpt got value type: %T, want bytes", optval.Val) } return ret, bytesval.Bytesval, errno } // GetSockOptInt calls getsockopt on the DUT and causes a fatal test failure // if it doesn't succeed. If more control over the int optval or error handling // is needed, use GetSockOptIntWithErrno. func (dut *DUT) GetSockOptInt(t *testing.T, sockfd, level, optname int32) int32 { t.Helper() ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) defer cancel() ret, intval, err := dut.GetSockOptIntWithErrno(ctx, t, sockfd, level, optname) if ret != 0 { t.Fatalf("failed to GetSockOptInt: %s", err) } return intval } // GetSockOptIntWithErrno calls getsockopt with an integer optval. func (dut *DUT) GetSockOptIntWithErrno(ctx context.Context, t *testing.T, sockfd, level, optname int32) (int32, int32, error) { t.Helper() ret, optval, errno := dut.getSockOpt(ctx, t, sockfd, level, optname, 0, pb.GetSockOptRequest_INT) intval, ok := optval.Val.(*pb.SockOptVal_Intval) if !ok { t.Fatalf("GetSockOpt got value type: %T, want int", optval.Val) } return ret, intval.Intval, errno } // GetSockOptTimeval calls getsockopt on the DUT and causes a fatal test failure // if it doesn't succeed. If more control over the timeout or error handling is // needed, use GetSockOptTimevalWithErrno. func (dut *DUT) GetSockOptTimeval(t *testing.T, sockfd, level, optname int32) unix.Timeval { t.Helper() ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) defer cancel() ret, timeval, err := dut.GetSockOptTimevalWithErrno(ctx, t, sockfd, level, optname) if ret != 0 { t.Fatalf("failed to GetSockOptTimeval: %s", err) } return timeval } // GetSockOptTimevalWithErrno calls getsockopt and returns a timeval. func (dut *DUT) GetSockOptTimevalWithErrno(ctx context.Context, t *testing.T, sockfd, level, optname int32) (int32, unix.Timeval, error) { t.Helper() ret, optval, errno := dut.getSockOpt(ctx, t, sockfd, level, optname, 0, pb.GetSockOptRequest_TIME) tv, ok := optval.Val.(*pb.SockOptVal_Timeval) if !ok { t.Fatalf("GetSockOpt got value type: %T, want timeval", optval.Val) } timeval := unix.Timeval{ Sec: tv.Timeval.Seconds, Usec: tv.Timeval.Microseconds, } return ret, timeval, errno } // Listen calls listen on the DUT and causes a fatal test failure if it doesn't // succeed. If more control over the timeout or error handling is needed, use // ListenWithErrno. func (dut *DUT) Listen(t *testing.T, sockfd, backlog int32) { t.Helper() ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) defer cancel() ret, err := dut.ListenWithErrno(ctx, t, sockfd, backlog) if ret != 0 { t.Fatalf("failed to listen: %s", err) } } // ListenWithErrno calls listen on the DUT. func (dut *DUT) ListenWithErrno(ctx context.Context, t *testing.T, sockfd, backlog int32) (int32, error) { t.Helper() req := pb.ListenRequest{ Sockfd: sockfd, Backlog: backlog, } resp, err := dut.posixServer.Listen(ctx, &req) if err != nil { t.Fatalf("failed to call Listen: %s", err) } return resp.GetRet(), syscall.Errno(resp.GetErrno_()) } // Poll calls poll on the DUT and causes a fatal test failure if it doesn't // succeed. If more control over error handling is needed, use PollWithErrno. // Only pollfds with non-empty revents are returned, the only way to tie the // response back to the original request is using the fd number. func (dut *DUT) Poll(t *testing.T, pfds []unix.PollFd, timeout time.Duration) []unix.PollFd { t.Helper() ctx := context.Background() var cancel context.CancelFunc if timeout >= 0 { ctx, cancel = context.WithTimeout(ctx, timeout+RPCTimeout) defer cancel() } ret, result, err := dut.PollWithErrno(ctx, t, pfds, timeout) if ret < 0 { t.Fatalf("failed to poll: %s", err) } return result } // PollWithErrno calls poll on the DUT. func (dut *DUT) PollWithErrno(ctx context.Context, t *testing.T, pfds []unix.PollFd, timeout time.Duration) (int32, []unix.PollFd, error) { t.Helper() req := pb.PollRequest{ TimeoutMillis: int32(timeout.Milliseconds()), } for _, pfd := range pfds { req.Pfds = append(req.Pfds, &pb.PollFd{ Fd: pfd.Fd, Events: uint32(pfd.Events), }) } resp, err := dut.posixServer.Poll(ctx, &req) if err != nil { t.Fatalf("failed to call Poll: %s", err) } if ret, npfds := resp.GetRet(), len(resp.GetPfds()); ret >= 0 && int(ret) != npfds { t.Fatalf("nonsensical poll response: ret(%d) != len(pfds)(%d)", ret, npfds) } var result []unix.PollFd for _, protoPfd := range resp.GetPfds() { result = append(result, unix.PollFd{ Fd: protoPfd.GetFd(), Revents: int16(protoPfd.GetEvents()), }) } return resp.GetRet(), result, 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(t *testing.T, sockfd int32, buf []byte, flags int32) int32 { t.Helper() ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) defer cancel() ret, err := dut.SendWithErrno(ctx, t, sockfd, buf, flags) if ret == -1 { t.Fatalf("failed to send: %s", err) } return ret } // SendWithErrno calls send on the DUT. func (dut *DUT) SendWithErrno(ctx context.Context, t *testing.T, sockfd int32, buf []byte, flags int32) (int32, error) { t.Helper() req := pb.SendRequest{ Sockfd: sockfd, Buf: buf, Flags: flags, } resp, err := dut.posixServer.Send(ctx, &req) if err != nil { t.Fatalf("failed to call Send: %s", err) } return resp.GetRet(), syscall.Errno(resp.GetErrno_()) } // SendTo calls sendto on the DUT and causes a fatal test failure if it doesn't // succeed. If more control over the timeout or error handling is needed, use // SendToWithErrno. func (dut *DUT) SendTo(t *testing.T, sockfd int32, buf []byte, flags int32, destAddr unix.Sockaddr) int32 { t.Helper() ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) defer cancel() ret, err := dut.SendToWithErrno(ctx, t, sockfd, buf, flags, destAddr) if ret == -1 { t.Fatalf("failed to sendto: %s", err) } return ret } // SendToWithErrno calls sendto on the DUT. func (dut *DUT) SendToWithErrno(ctx context.Context, t *testing.T, sockfd int32, buf []byte, flags int32, destAddr unix.Sockaddr) (int32, error) { t.Helper() req := pb.SendToRequest{ Sockfd: sockfd, Buf: buf, Flags: flags, DestAddr: dut.sockaddrToProto(t, destAddr), } resp, err := dut.posixServer.SendTo(ctx, &req) if err != nil { t.Fatalf("failed to call SendTo: %s", err) } return resp.GetRet(), syscall.Errno(resp.GetErrno_()) } // SetNonBlocking will set O_NONBLOCK flag for fd if nonblocking // is true, otherwise it will clear the flag. func (dut *DUT) SetNonBlocking(t *testing.T, fd int32, nonblocking bool) { t.Helper() flags := dut.Fcntl(t, fd, unix.F_GETFL, 0) if nonblocking { flags |= unix.O_NONBLOCK } else { flags &= ^unix.O_NONBLOCK } dut.Fcntl(t, fd, unix.F_SETFL, flags) } func (dut *DUT) setSockOpt(ctx context.Context, t *testing.T, sockfd, level, optname int32, optval *pb.SockOptVal) (int32, error) { t.Helper() req := pb.SetSockOptRequest{ Sockfd: sockfd, Level: level, Optname: optname, Optval: optval, } resp, err := dut.posixServer.SetSockOpt(ctx, &req) if err != nil { t.Fatalf("failed to call SetSockOpt: %s", err) } return resp.GetRet(), 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(t *testing.T, sockfd, level, optname int32, optval []byte) { t.Helper() ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) defer cancel() ret, err := dut.SetSockOptWithErrno(ctx, t, sockfd, level, optname, optval) if ret != 0 { t.Fatalf("failed to SetSockOpt: %s", err) } } // SetSockOptWithErrno calls setsockopt on the DUT. Because endianess and the // width of values might differ between the testbench and DUT architectures, // prefer to use a more specific SetSockOptXxxWithErrno function. func (dut *DUT) SetSockOptWithErrno(ctx context.Context, t *testing.T, sockfd, level, optname int32, optval []byte) (int32, error) { t.Helper() return dut.setSockOpt(ctx, t, sockfd, level, optname, &pb.SockOptVal{Val: &pb.SockOptVal_Bytesval{optval}}) } // SetSockOptInt calls setsockopt on the DUT and causes a fatal test failure // if it doesn't succeed. If more control over the int optval or error handling // is needed, use SetSockOptIntWithErrno. func (dut *DUT) SetSockOptInt(t *testing.T, sockfd, level, optname, optval int32) { t.Helper() ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) defer cancel() ret, err := dut.SetSockOptIntWithErrno(ctx, t, sockfd, level, optname, optval) if ret != 0 { t.Fatalf("failed to SetSockOptInt: %s", err) } } // SetSockOptIntWithErrno calls setsockopt with an integer optval. func (dut *DUT) SetSockOptIntWithErrno(ctx context.Context, t *testing.T, sockfd, level, optname, optval int32) (int32, error) { t.Helper() return dut.setSockOpt(ctx, t, sockfd, level, optname, &pb.SockOptVal{Val: &pb.SockOptVal_Intval{optval}}) } // SetSockOptTimeval calls setsockopt on the DUT and causes a fatal test failure // if it doesn't succeed. If more control over the timeout or error handling is // needed, use SetSockOptTimevalWithErrno. func (dut *DUT) SetSockOptTimeval(t *testing.T, sockfd, level, optname int32, tv *unix.Timeval) { t.Helper() ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) defer cancel() ret, err := dut.SetSockOptTimevalWithErrno(ctx, t, sockfd, level, optname, tv) if ret != 0 { t.Fatalf("failed to SetSockOptTimeval: %s", err) } } // SetSockOptTimevalWithErrno calls setsockopt with the timeval converted to // bytes. func (dut *DUT) SetSockOptTimevalWithErrno(ctx context.Context, t *testing.T, sockfd, level, optname int32, tv *unix.Timeval) (int32, error) { t.Helper() timeval := pb.Timeval{ Seconds: int64(tv.Sec), Microseconds: int64(tv.Usec), } return dut.setSockOpt(ctx, t, sockfd, level, optname, &pb.SockOptVal{Val: &pb.SockOptVal_Timeval{&timeval}}) } // Socket calls socket on the DUT and returns the file descriptor. If socket // fails on the DUT, the test ends. func (dut *DUT) Socket(t *testing.T, domain, typ, proto int32) int32 { t.Helper() fd, err := dut.SocketWithErrno(t, domain, typ, proto) if fd < 0 { t.Fatalf("failed to create socket: %s", err) } return fd } // SocketWithErrno calls socket on the DUT and returns the fd and errno. func (dut *DUT) SocketWithErrno(t *testing.T, domain, typ, proto int32) (int32, error) { t.Helper() req := pb.SocketRequest{ Domain: domain, Type: typ, Protocol: proto, } ctx := context.Background() resp, err := dut.posixServer.Socket(ctx, &req) if err != nil { 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(t *testing.T, sockfd, len, flags int32) []byte { t.Helper() ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) defer cancel() ret, buf, err := dut.RecvWithErrno(ctx, t, sockfd, len, flags) if ret == -1 { t.Fatalf("failed to recv: %s", err) } return buf } // RecvWithErrno calls recv on the DUT. func (dut *DUT) RecvWithErrno(ctx context.Context, t *testing.T, sockfd, len, flags int32) (int32, []byte, error) { t.Helper() req := pb.RecvRequest{ Sockfd: sockfd, Len: len, Flags: flags, } resp, err := dut.posixServer.Recv(ctx, &req) if err != nil { t.Fatalf("failed to call Recv: %s", err) } return resp.GetRet(), resp.GetBuf(), syscall.Errno(resp.GetErrno_()) } // SetSockLingerOption sets SO_LINGER socket option on the DUT. func (dut *DUT) SetSockLingerOption(t *testing.T, sockfd int32, timeout time.Duration, enable bool) { var linger unix.Linger if enable { linger.Onoff = 1 } linger.Linger = int32(timeout / time.Second) buf := make([]byte, 8) binary.LittleEndian.PutUint32(buf, uint32(linger.Onoff)) binary.LittleEndian.PutUint32(buf[4:], uint32(linger.Linger)) dut.SetSockOpt(t, sockfd, unix.SOL_SOCKET, unix.SO_LINGER, buf) } // Shutdown calls shutdown on the DUT and causes a fatal test failure if it // doesn't succeed. If more control over the timeout or error handling is // needed, use ShutdownWithErrno. func (dut *DUT) Shutdown(t *testing.T, fd, how int32) error { t.Helper() ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) defer cancel() return dut.ShutdownWithErrno(ctx, t, fd, how) } // ShutdownWithErrno calls shutdown on the DUT. func (dut *DUT) ShutdownWithErrno(ctx context.Context, t *testing.T, fd, how int32) error { t.Helper() req := pb.ShutdownRequest{ Fd: fd, How: how, } resp, err := dut.posixServer.Shutdown(ctx, &req) if err != nil { t.Fatalf("failed to call Shutdown: %s", err) } return syscall.Errno(resp.GetErrno_()) }