mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
netstack: Refactor tcpip.Endpoint.Read
Read now takes a destination io.Writer, count, options. Keeping the method name Read, in contrast to the Write method. This enables: * direct transfer of views under VV * zero copy It also eliminates the need for sentry to keep a slice of view because userspace had requested a read that is smaller than the view returned, removing the complexity there. Read/Peek/ReadPacket are now consolidated together and some duplicate code is removed. PiperOrigin-RevId: 350636322
This commit is contained in:
@@ -25,7 +25,6 @@ go_library(
|
||||
"//pkg/marshal",
|
||||
"//pkg/marshal/primitive",
|
||||
"//pkg/metric",
|
||||
"//pkg/safemem",
|
||||
"//pkg/sentry/arch",
|
||||
"//pkg/sentry/device",
|
||||
"//pkg/sentry/fs",
|
||||
|
||||
@@ -28,9 +28,9 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"reflect"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@@ -43,7 +43,6 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/marshal"
|
||||
"gvisor.dev/gvisor/pkg/marshal/primitive"
|
||||
"gvisor.dev/gvisor/pkg/metric"
|
||||
"gvisor.dev/gvisor/pkg/safemem"
|
||||
"gvisor.dev/gvisor/pkg/sentry/arch"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fs/fsutil"
|
||||
@@ -308,16 +307,8 @@ type socketOpsCommon struct {
|
||||
skType linux.SockType
|
||||
protocol int
|
||||
|
||||
// readViewHasData is 1 iff readView has data to be read, 0 otherwise.
|
||||
// Must be accessed using atomic operations. It must only be written
|
||||
// with readMu held but can be read without holding readMu. The latter
|
||||
// is required to avoid deadlocks in epoll Readiness checks.
|
||||
readViewHasData uint32
|
||||
|
||||
// readMu protects access to the below fields.
|
||||
readMu sync.Mutex `state:"nosave"`
|
||||
// readView contains the remaining payload from the last packet.
|
||||
readView buffer.View
|
||||
// readCM holds control message information for the last packet read
|
||||
// from Endpoint.
|
||||
readCM socket.IPControlMessages
|
||||
@@ -336,8 +327,8 @@ type socketOpsCommon struct {
|
||||
// valid when timestampValid is true. It is protected by readMu.
|
||||
timestampNS int64
|
||||
|
||||
// sockOptInq corresponds to TCP_INQ. It is implemented at this level
|
||||
// because it takes into account data from readView.
|
||||
// TODO(b/153685824): Move this to SocketOptions.
|
||||
// sockOptInq corresponds to TCP_INQ.
|
||||
sockOptInq bool
|
||||
}
|
||||
|
||||
@@ -377,41 +368,23 @@ func (s *socketOpsCommon) isPacketBased() bool {
|
||||
return s.skType == linux.SOCK_DGRAM || s.skType == linux.SOCK_SEQPACKET || s.skType == linux.SOCK_RDM || s.skType == linux.SOCK_RAW
|
||||
}
|
||||
|
||||
// fetchReadView updates the readView field of the socket if it's currently
|
||||
// empty. It assumes that the socket is locked.
|
||||
//
|
||||
// Precondition: s.readMu must be held.
|
||||
func (s *socketOpsCommon) fetchReadView() *syserr.Error {
|
||||
if len(s.readView) > 0 {
|
||||
return nil
|
||||
}
|
||||
s.readView = nil
|
||||
s.sender = tcpip.FullAddress{}
|
||||
s.linkPacketInfo = tcpip.LinkPacketInfo{}
|
||||
func (s *socketOpsCommon) readLocked(dst io.Writer, count int, peek bool) (numRead, numTotal int, serr *syserr.Error) {
|
||||
res, err := s.Endpoint.Read(dst, count, tcpip.ReadOptions{
|
||||
Peek: peek,
|
||||
NeedRemoteAddr: true,
|
||||
NeedLinkPacketInfo: true,
|
||||
})
|
||||
|
||||
var v buffer.View
|
||||
var cms tcpip.ControlMessages
|
||||
var err *tcpip.Error
|
||||
// Assign these anyways.
|
||||
s.readCM = socket.NewIPControlMessages(s.family, res.ControlMessages)
|
||||
s.sender = res.RemoteAddr
|
||||
s.linkPacketInfo = res.LinkPacketInfo
|
||||
|
||||
switch e := s.Endpoint.(type) {
|
||||
// The ordering of these interfaces matters. The most specific
|
||||
// interfaces must be specified before the more generic Endpoint
|
||||
// interface.
|
||||
case tcpip.PacketEndpoint:
|
||||
v, cms, err = e.ReadPacket(&s.sender, &s.linkPacketInfo)
|
||||
case tcpip.Endpoint:
|
||||
v, cms, err = e.Read(&s.sender)
|
||||
}
|
||||
if err != nil {
|
||||
atomic.StoreUint32(&s.readViewHasData, 0)
|
||||
return syserr.TranslateNetstackError(err)
|
||||
return 0, 0, syserr.TranslateNetstackError(err)
|
||||
}
|
||||
|
||||
s.readView = v
|
||||
s.readCM = socket.NewIPControlMessages(s.family, cms)
|
||||
atomic.StoreUint32(&s.readViewHasData, 1)
|
||||
|
||||
return nil
|
||||
return res.Count, res.Total, nil
|
||||
}
|
||||
|
||||
// Release implements fs.FileOperations.Release.
|
||||
@@ -460,38 +433,14 @@ func (s *SocketOperations) Read(ctx context.Context, _ *fs.File, dst usermem.IOS
|
||||
// WriteTo implements fs.FileOperations.WriteTo.
|
||||
func (s *SocketOperations) WriteTo(ctx context.Context, _ *fs.File, dst io.Writer, count int64, dup bool) (int64, error) {
|
||||
s.readMu.Lock()
|
||||
defer s.readMu.Unlock()
|
||||
|
||||
// Copy as much data as possible.
|
||||
done := int64(0)
|
||||
for count > 0 {
|
||||
// This may return a blocking error.
|
||||
if err := s.fetchReadView(); err != nil {
|
||||
s.readMu.Unlock()
|
||||
return done, err.ToError()
|
||||
}
|
||||
|
||||
// Write to the underlying file.
|
||||
n, err := dst.Write(s.readView)
|
||||
done += int64(n)
|
||||
count -= int64(n)
|
||||
if dup {
|
||||
// That's all we support for dup. This is generally
|
||||
// supported by any Linux system calls, but the
|
||||
// expectation is that now a caller will call read to
|
||||
// actually remove these bytes from the socket.
|
||||
break
|
||||
}
|
||||
|
||||
// Drop that part of the view.
|
||||
s.readView.TrimFront(n)
|
||||
if err != nil {
|
||||
s.readMu.Unlock()
|
||||
return done, err
|
||||
}
|
||||
// This may return a blocking error.
|
||||
n, _, err := s.readLocked(dst, int(count), dup /* peek */)
|
||||
if err != nil {
|
||||
return 0, err.ToError()
|
||||
}
|
||||
|
||||
s.readMu.Unlock()
|
||||
return done, nil
|
||||
return int64(n), nil
|
||||
}
|
||||
|
||||
// ioSequencePayload implements tcpip.Payload.
|
||||
@@ -627,17 +576,7 @@ func (s *SocketOperations) ReadFrom(ctx context.Context, _ *fs.File, r io.Reader
|
||||
|
||||
// Readiness returns a mask of ready events for socket s.
|
||||
func (s *socketOpsCommon) Readiness(mask waiter.EventMask) waiter.EventMask {
|
||||
r := s.Endpoint.Readiness(mask)
|
||||
|
||||
// Check our cached value iff the caller asked for readability and the
|
||||
// endpoint itself is currently not readable.
|
||||
if (mask & ^r & waiter.EventIn) != 0 {
|
||||
if atomic.LoadUint32(&s.readViewHasData) == 1 {
|
||||
r |= waiter.EventIn
|
||||
}
|
||||
}
|
||||
|
||||
return r
|
||||
return s.Endpoint.Readiness(mask)
|
||||
}
|
||||
|
||||
func (s *socketOpsCommon) checkFamily(family uint16, exact bool) *syserr.Error {
|
||||
@@ -2618,66 +2557,20 @@ func (s *socketOpsCommon) GetPeerName(t *kernel.Task) (linux.SockAddr, uint32, *
|
||||
return a, l, nil
|
||||
}
|
||||
|
||||
// coalescingRead is the fast path for non-blocking, non-peek, stream-based
|
||||
// case. It coalesces as many packets as possible before returning to the
|
||||
// caller.
|
||||
// streamRead is the fast path for non-blocking, non-peek, stream-based socket.
|
||||
//
|
||||
// Precondition: s.readMu must be locked.
|
||||
func (s *socketOpsCommon) coalescingRead(ctx context.Context, dst usermem.IOSequence, discard bool) (int, *syserr.Error) {
|
||||
var err *syserr.Error
|
||||
var copied int
|
||||
|
||||
// Copy as many views as possible into the user-provided buffer.
|
||||
for {
|
||||
// Always do at least one fetchReadView, even if the number of bytes to
|
||||
// read is 0.
|
||||
err = s.fetchReadView()
|
||||
if err != nil || len(s.readView) == 0 {
|
||||
break
|
||||
}
|
||||
if dst.NumBytes() == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
var n int
|
||||
var e error
|
||||
if discard {
|
||||
n = len(s.readView)
|
||||
if int64(n) > dst.NumBytes() {
|
||||
n = int(dst.NumBytes())
|
||||
}
|
||||
} else {
|
||||
n, e = dst.CopyOut(ctx, s.readView)
|
||||
// Set the control message, even if 0 bytes were read.
|
||||
if e == nil {
|
||||
s.updateTimestamp()
|
||||
}
|
||||
}
|
||||
copied += n
|
||||
s.readView.TrimFront(n)
|
||||
|
||||
dst = dst.DropFirst(n)
|
||||
if e != nil {
|
||||
err = syserr.FromError(e)
|
||||
break
|
||||
}
|
||||
// If we are done reading requested data then stop.
|
||||
if dst.NumBytes() == 0 {
|
||||
break
|
||||
}
|
||||
func (s *socketOpsCommon) streamRead(ctx context.Context, dst io.Writer, count int) (int, *syserr.Error) {
|
||||
// Always do at least one read, even if the number of bytes to read is 0.
|
||||
var n int
|
||||
n, _, err := s.readLocked(dst, count, false /* peek */)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if len(s.readView) == 0 {
|
||||
atomic.StoreUint32(&s.readViewHasData, 0)
|
||||
if n > 0 {
|
||||
s.Endpoint.ModerateRecvBuf(n)
|
||||
}
|
||||
|
||||
// If we managed to copy something, we must deliver it.
|
||||
if copied > 0 {
|
||||
s.Endpoint.ModerateRecvBuf(copied)
|
||||
return copied, nil
|
||||
}
|
||||
|
||||
return 0, err
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (s *socketOpsCommon) fillCmsgInq(cmsg *socket.ControlMessages) {
|
||||
@@ -2689,7 +2582,7 @@ func (s *socketOpsCommon) fillCmsgInq(cmsg *socket.ControlMessages) {
|
||||
return
|
||||
}
|
||||
cmsg.IP.HasInq = true
|
||||
cmsg.IP.Inq = int32(len(s.readView) + rcvBufUsed)
|
||||
cmsg.IP.Inq = int32(rcvBufUsed)
|
||||
}
|
||||
|
||||
func toLinuxPacketType(pktType tcpip.PacketType) uint8 {
|
||||
@@ -2726,7 +2619,21 @@ func (s *socketOpsCommon) nonBlockingRead(ctx context.Context, dst usermem.IOSeq
|
||||
// bytes of data to be discarded, rather than passed back in a
|
||||
// caller-supplied buffer.
|
||||
s.readMu.Lock()
|
||||
n, err := s.coalescingRead(ctx, dst, trunc)
|
||||
|
||||
var w io.Writer
|
||||
if trunc {
|
||||
w = ioutil.Discard
|
||||
} else {
|
||||
w = dst.Writer(ctx)
|
||||
}
|
||||
|
||||
n, err := s.streamRead(ctx, w, int(dst.NumBytes()))
|
||||
|
||||
if err == nil && !trunc {
|
||||
// Set the control message, even if 0 bytes were read.
|
||||
s.updateTimestamp()
|
||||
}
|
||||
|
||||
cmsg := s.controlMessages()
|
||||
s.fillCmsgInq(&cmsg)
|
||||
s.readMu.Unlock()
|
||||
@@ -2736,18 +2643,32 @@ func (s *socketOpsCommon) nonBlockingRead(ctx context.Context, dst usermem.IOSeq
|
||||
s.readMu.Lock()
|
||||
defer s.readMu.Unlock()
|
||||
|
||||
if err := s.fetchReadView(); err != nil {
|
||||
// MSG_TRUNC with MSG_PEEK on a TCP socket returns the
|
||||
// amount that could be read, and does not write to buffer.
|
||||
isTCPPeekTrunc := !isPacket && peek && trunc
|
||||
|
||||
var w io.Writer
|
||||
if isTCPPeekTrunc {
|
||||
w = ioutil.Discard
|
||||
} else {
|
||||
w = dst.Writer(ctx)
|
||||
}
|
||||
|
||||
var numRead, numTotal int
|
||||
var err *syserr.Error
|
||||
numRead, numTotal, err = s.readLocked(w, int(dst.NumBytes()), peek)
|
||||
if err != nil {
|
||||
return 0, 0, nil, 0, socket.ControlMessages{}, err
|
||||
}
|
||||
|
||||
if !isPacket && peek && trunc {
|
||||
// MSG_TRUNC with MSG_PEEK on a TCP socket returns the
|
||||
// amount that could be read.
|
||||
if isTCPPeekTrunc {
|
||||
// TCP endpoint does not return the total bytes in buffer as numTotal.
|
||||
// We need to query it from socket option.
|
||||
rql, err := s.Endpoint.GetSockOptInt(tcpip.ReceiveQueueSizeOption)
|
||||
if err != nil {
|
||||
return 0, 0, nil, 0, socket.ControlMessages{}, syserr.TranslateNetstackError(err)
|
||||
}
|
||||
available := len(s.readView) + int(rql)
|
||||
available := int(rql)
|
||||
bufLen := int(dst.NumBytes())
|
||||
if available < bufLen {
|
||||
return available, 0, nil, 0, socket.ControlMessages{}, nil
|
||||
@@ -2755,11 +2676,9 @@ func (s *socketOpsCommon) nonBlockingRead(ctx context.Context, dst usermem.IOSeq
|
||||
return bufLen, 0, nil, 0, socket.ControlMessages{}, nil
|
||||
}
|
||||
|
||||
n, err := dst.CopyOut(ctx, s.readView)
|
||||
// Set the control message, even if 0 bytes were read.
|
||||
if err == nil {
|
||||
s.updateTimestamp()
|
||||
}
|
||||
s.updateTimestamp()
|
||||
|
||||
var addr linux.SockAddr
|
||||
var addrLen uint32
|
||||
if isPacket && senderRequested {
|
||||
@@ -2772,58 +2691,33 @@ func (s *socketOpsCommon) nonBlockingRead(ctx context.Context, dst usermem.IOSeq
|
||||
}
|
||||
|
||||
if peek {
|
||||
if l := len(s.readView); trunc && l > n {
|
||||
if trunc && numTotal > numRead {
|
||||
// isPacket must be true.
|
||||
return l, linux.MSG_TRUNC, addr, addrLen, s.controlMessages(), syserr.FromError(err)
|
||||
return numTotal, linux.MSG_TRUNC, addr, addrLen, s.controlMessages(), nil
|
||||
}
|
||||
|
||||
if isPacket || err != nil {
|
||||
return n, 0, addr, addrLen, s.controlMessages(), syserr.FromError(err)
|
||||
}
|
||||
|
||||
// We need to peek beyond the first message.
|
||||
dst = dst.DropFirst(n)
|
||||
num, err := dst.CopyOutFrom(ctx, safemem.FromVecReaderFunc{func(dsts [][]byte) (int64, error) {
|
||||
n, err := s.Endpoint.Peek(dsts)
|
||||
// TODO(b/78348848): Handle peek timestamp.
|
||||
if err != nil {
|
||||
return int64(n), syserr.TranslateNetstackError(err).ToError()
|
||||
}
|
||||
return int64(n), nil
|
||||
}})
|
||||
n += int(num)
|
||||
if err == syserror.ErrWouldBlock && n > 0 {
|
||||
// We got some data, so no need to return an error.
|
||||
err = nil
|
||||
}
|
||||
return n, 0, nil, 0, s.controlMessages(), syserr.FromError(err)
|
||||
return numRead, 0, nil, 0, s.controlMessages(), nil
|
||||
}
|
||||
|
||||
var msgLen int
|
||||
if isPacket {
|
||||
msgLen = len(s.readView)
|
||||
s.readView = nil
|
||||
msgLen = numTotal
|
||||
} else {
|
||||
msgLen = int(n)
|
||||
s.readView.TrimFront(int(n))
|
||||
}
|
||||
|
||||
if len(s.readView) == 0 {
|
||||
atomic.StoreUint32(&s.readViewHasData, 0)
|
||||
msgLen = numRead
|
||||
}
|
||||
|
||||
var flags int
|
||||
if msgLen > int(n) {
|
||||
if msgLen > numRead {
|
||||
flags |= linux.MSG_TRUNC
|
||||
}
|
||||
|
||||
n := numRead
|
||||
if trunc {
|
||||
n = msgLen
|
||||
}
|
||||
|
||||
cmsg := s.controlMessages()
|
||||
s.fillCmsgInq(&cmsg)
|
||||
return n, flags, addr, addrLen, cmsg, syserr.FromError(err)
|
||||
return n, flags, addr, addrLen, cmsg, nil
|
||||
}
|
||||
|
||||
func (s *socketOpsCommon) controlMessages() socket.ControlMessages {
|
||||
@@ -3090,11 +2984,6 @@ func (s *socketOpsCommon) ioctl(ctx context.Context, io usermem.IO, args arch.Sy
|
||||
return 0, syserr.TranslateNetstackError(terr).ToError()
|
||||
}
|
||||
|
||||
// Add bytes removed from the endpoint but not yet sent to the caller.
|
||||
s.readMu.Lock()
|
||||
v += len(s.readView)
|
||||
s.readMu.Unlock()
|
||||
|
||||
if v > math.MaxInt32 {
|
||||
v = math.MaxInt32
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ var (
|
||||
ErrInvalidOptionValue = New(tcpip.ErrInvalidOptionValue.String(), linux.EINVAL)
|
||||
ErrBroadcastDisabled = New(tcpip.ErrBroadcastDisabled.String(), linux.EACCES)
|
||||
ErrNotPermittedNet = New(tcpip.ErrNotPermitted.String(), linux.EPERM)
|
||||
ErrBadBuffer = New(tcpip.ErrBadBuffer.String(), linux.EFAULT)
|
||||
)
|
||||
|
||||
var netstackErrorTranslations map[string]*Error
|
||||
@@ -100,6 +101,7 @@ func init() {
|
||||
addErrMapping(tcpip.ErrBroadcastDisabled, ErrBroadcastDisabled)
|
||||
addErrMapping(tcpip.ErrNotPermitted, ErrNotPermittedNet)
|
||||
addErrMapping(tcpip.ErrAddressFamilyNotSupported, ErrAddressFamilyNotSupported)
|
||||
addErrMapping(tcpip.ErrBadBuffer, ErrBadBuffer)
|
||||
}
|
||||
|
||||
// TranslateNetstackError converts an error from the tcpip package to a sentry
|
||||
|
||||
@@ -286,45 +286,47 @@ type opErrorer interface {
|
||||
|
||||
// commonRead implements the common logic between net.Conn.Read and
|
||||
// net.PacketConn.ReadFrom.
|
||||
func commonRead(ep tcpip.Endpoint, wq *waiter.Queue, deadline <-chan struct{}, addr *tcpip.FullAddress, errorer opErrorer, dontWait bool) ([]byte, error) {
|
||||
func commonRead(b []byte, ep tcpip.Endpoint, wq *waiter.Queue, deadline <-chan struct{}, addr *tcpip.FullAddress, errorer opErrorer) (int, error) {
|
||||
select {
|
||||
case <-deadline:
|
||||
return nil, errorer.newOpError("read", &timeoutError{})
|
||||
return 0, errorer.newOpError("read", &timeoutError{})
|
||||
default:
|
||||
}
|
||||
|
||||
read, _, err := ep.Read(addr)
|
||||
w := tcpip.SliceWriter(b)
|
||||
opts := tcpip.ReadOptions{NeedRemoteAddr: addr != nil}
|
||||
res, err := ep.Read(&w, len(b), opts)
|
||||
|
||||
if err == tcpip.ErrWouldBlock {
|
||||
if dontWait {
|
||||
return nil, errWouldBlock
|
||||
}
|
||||
// Create wait queue entry that notifies a channel.
|
||||
waitEntry, notifyCh := waiter.NewChannelEntry(nil)
|
||||
wq.EventRegister(&waitEntry, waiter.EventIn)
|
||||
defer wq.EventUnregister(&waitEntry)
|
||||
for {
|
||||
read, _, err = ep.Read(addr)
|
||||
res, err = ep.Read(&w, len(b), opts)
|
||||
if err != tcpip.ErrWouldBlock {
|
||||
break
|
||||
}
|
||||
select {
|
||||
case <-deadline:
|
||||
return nil, errorer.newOpError("read", &timeoutError{})
|
||||
return 0, errorer.newOpError("read", &timeoutError{})
|
||||
case <-notifyCh:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err == tcpip.ErrClosedForReceive {
|
||||
return nil, io.EOF
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, errorer.newOpError("read", errors.New(err.String()))
|
||||
return 0, errorer.newOpError("read", errors.New(err.String()))
|
||||
}
|
||||
|
||||
return read, nil
|
||||
if addr != nil {
|
||||
*addr = res.RemoteAddr
|
||||
}
|
||||
return res.Count, nil
|
||||
}
|
||||
|
||||
// Read implements net.Conn.Read.
|
||||
@@ -334,31 +336,11 @@ func (c *TCPConn) Read(b []byte) (int, error) {
|
||||
|
||||
deadline := c.readCancel()
|
||||
|
||||
numRead := 0
|
||||
defer func() {
|
||||
if numRead != 0 {
|
||||
c.ep.ModerateRecvBuf(numRead)
|
||||
}
|
||||
}()
|
||||
for numRead != len(b) {
|
||||
if len(c.read) == 0 {
|
||||
var err error
|
||||
c.read, err = commonRead(c.ep, c.wq, deadline, nil, c, numRead != 0)
|
||||
if err != nil {
|
||||
if numRead != 0 {
|
||||
return numRead, nil
|
||||
}
|
||||
return numRead, err
|
||||
}
|
||||
}
|
||||
n := copy(b[numRead:], c.read)
|
||||
c.read.TrimFront(n)
|
||||
numRead += n
|
||||
if len(c.read) == 0 {
|
||||
c.read = nil
|
||||
}
|
||||
n, err := commonRead(b, c.ep, c.wq, deadline, nil, c)
|
||||
if n != 0 {
|
||||
c.ep.ModerateRecvBuf(n)
|
||||
}
|
||||
return numRead, nil
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Write implements net.Conn.Write.
|
||||
@@ -652,12 +634,11 @@ func (c *UDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
deadline := c.readCancel()
|
||||
|
||||
var addr tcpip.FullAddress
|
||||
read, err := commonRead(c.ep, c.wq, deadline, &addr, c, false)
|
||||
n, err := commonRead(b, c.ep, c.wq, deadline, &addr, c)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
return copy(b, read), fullToUDPAddr(addr), nil
|
||||
return n, fullToUDPAddr(addr), nil
|
||||
}
|
||||
|
||||
func (c *UDPConn) Write(b []byte) (int, error) {
|
||||
|
||||
@@ -105,18 +105,18 @@ func (vv *VectorisedView) TrimFront(count int) {
|
||||
}
|
||||
|
||||
// Read implements io.Reader.
|
||||
func (vv *VectorisedView) Read(v View) (copied int, err error) {
|
||||
count := len(v)
|
||||
func (vv *VectorisedView) Read(b []byte) (copied int, err error) {
|
||||
count := len(b)
|
||||
for count > 0 && len(vv.views) > 0 {
|
||||
if count < len(vv.views[0]) {
|
||||
vv.size -= count
|
||||
copy(v[copied:], vv.views[0][:count])
|
||||
copy(b[copied:], vv.views[0][:count])
|
||||
vv.views[0].TrimFront(count)
|
||||
copied += count
|
||||
return copied, nil
|
||||
}
|
||||
count -= len(vv.views[0])
|
||||
copy(v[copied:], vv.views[0])
|
||||
copy(b[copied:], vv.views[0])
|
||||
copied += len(vv.views[0])
|
||||
vv.removeFirst()
|
||||
}
|
||||
@@ -145,6 +145,35 @@ func (vv *VectorisedView) ReadToVV(dstVV *VectorisedView, count int) (copied int
|
||||
return copied
|
||||
}
|
||||
|
||||
// ReadTo reads up to count bytes from vv to dst. It also removes them from vv
|
||||
// unless peek is true.
|
||||
func (vv *VectorisedView) ReadTo(dst io.Writer, count int, peek bool) (int, error) {
|
||||
var err error
|
||||
done := 0
|
||||
for _, v := range vv.Views() {
|
||||
remaining := count - done
|
||||
if remaining <= 0 {
|
||||
break
|
||||
}
|
||||
if len(v) > remaining {
|
||||
v = v[:remaining]
|
||||
}
|
||||
|
||||
var n int
|
||||
n, err = dst.Write(v)
|
||||
if n > 0 {
|
||||
done += n
|
||||
}
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !peek {
|
||||
vv.TrimFront(done)
|
||||
}
|
||||
return done, err
|
||||
}
|
||||
|
||||
// CapLength irreversibly reduces the length of the vectorised view.
|
||||
func (vv *VectorisedView) CapLength(length int) {
|
||||
if length < 0 {
|
||||
|
||||
@@ -235,14 +235,16 @@ func TestToClone(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestVVReadToVV(t *testing.T) {
|
||||
testCases := []struct {
|
||||
comment string
|
||||
vv VectorisedView
|
||||
bytesToRead int
|
||||
wantBytes string
|
||||
leftVV VectorisedView
|
||||
}{
|
||||
type readToTestCases struct {
|
||||
comment string
|
||||
vv VectorisedView
|
||||
bytesToRead int
|
||||
wantBytes string
|
||||
leftVV VectorisedView
|
||||
}
|
||||
|
||||
func createReadToTestCases() []readToTestCases {
|
||||
return []readToTestCases{
|
||||
{
|
||||
comment: "large VV, short read",
|
||||
vv: vv(30, "012345678901234567890123456789"),
|
||||
@@ -279,8 +281,10 @@ func TestVVReadToVV(t *testing.T) {
|
||||
leftVV: vv(0, ""),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
func TestVVReadToVV(t *testing.T) {
|
||||
for _, tc := range createReadToTestCases() {
|
||||
t.Run(tc.comment, func(t *testing.T) {
|
||||
var readTo VectorisedView
|
||||
inSize := tc.vv.Size()
|
||||
@@ -301,6 +305,52 @@ func TestVVReadToVV(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestVVReadTo(t *testing.T) {
|
||||
for _, tc := range createReadToTestCases() {
|
||||
t.Run(tc.comment, func(t *testing.T) {
|
||||
var dst bytes.Buffer
|
||||
origSize := tc.vv.Size()
|
||||
copied, err := tc.vv.ReadTo(&dst, tc.bytesToRead, false /* peek */)
|
||||
if got, want := copied, len(tc.wantBytes); err != nil || got != want {
|
||||
t.Errorf("got ReadTo(&dst, %d, false) = %d, %v; want %d, nil", tc.bytesToRead, got, err, want)
|
||||
}
|
||||
if got, want := string(dst.Bytes()), tc.wantBytes; got != want {
|
||||
t.Errorf("got dst = %q, want %q", got, want)
|
||||
}
|
||||
if got, want := tc.vv.Size(), origSize-copied; got != want {
|
||||
t.Errorf("got after-read tc.vv.Size() = %d, want %d", got, want)
|
||||
}
|
||||
if got, want := string(tc.vv.ToView()), string(tc.leftVV.ToView()); got != want {
|
||||
t.Errorf("got after-read data in tc.vv = %q, want %q", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVVReadToPeek(t *testing.T) {
|
||||
for _, tc := range createReadToTestCases() {
|
||||
t.Run(tc.comment, func(t *testing.T) {
|
||||
var dst bytes.Buffer
|
||||
origSize := tc.vv.Size()
|
||||
origData := string(tc.vv.ToView())
|
||||
copied, err := tc.vv.ReadTo(&dst, tc.bytesToRead, true /* peek */)
|
||||
if got, want := copied, len(tc.wantBytes); err != nil || got != want {
|
||||
t.Errorf("got ReadTo(&dst, %d, false) = %d, %v; want %d, nil", tc.bytesToRead, got, err, want)
|
||||
}
|
||||
if got, want := string(dst.Bytes()), tc.wantBytes; got != want {
|
||||
t.Errorf("got dst = %q, want %q", got, want)
|
||||
}
|
||||
// Expect tc.vv is unchanged.
|
||||
if got, want := tc.vv.Size(), origSize; got != want {
|
||||
t.Errorf("got after-read tc.vv.Size() = %d, want %d", got, want)
|
||||
}
|
||||
if got, want := string(tc.vv.ToView()), origData; got != want {
|
||||
t.Errorf("got after-read data in tc.vv = %q, want %q", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVVRead(t *testing.T) {
|
||||
testCases := []struct {
|
||||
comment string
|
||||
|
||||
@@ -1603,3 +1603,15 @@ func IPv6RouterAlert(want header.IPv6RouterAlertValue) IPv6ExtHdrOptionChecker {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IgnoreCmpPath returns a cmp.Option that ignores listed field paths.
|
||||
func IgnoreCmpPath(paths ...string) cmp.Option {
|
||||
ignores := map[string]struct{}{}
|
||||
for _, path := range paths {
|
||||
ignores[path] = struct{}{}
|
||||
}
|
||||
return cmp.FilterPath(func(path cmp.Path) bool {
|
||||
_, ok := ignores[path.String()]
|
||||
return ok
|
||||
}, cmp.Ignore())
|
||||
}
|
||||
|
||||
@@ -15,9 +15,11 @@
|
||||
package ipv4_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"net"
|
||||
"testing"
|
||||
@@ -2408,18 +2410,26 @@ func TestReceiveFragments(t *testing.T) {
|
||||
t.Errorf("got UDP Rx Packets = %d, want = %d", got, want)
|
||||
}
|
||||
|
||||
const rcvSize = 65536 // Account for reassembled packets.
|
||||
for i, expectedPayload := range test.expectedPayloads {
|
||||
gotPayload, _, err := ep.Read(nil)
|
||||
var buf bytes.Buffer
|
||||
result, err := ep.Read(&buf, rcvSize, tcpip.ReadOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("(i=%d) Read(nil): %s", i, err)
|
||||
t.Fatalf("(i=%d) Read: %s", i, err)
|
||||
}
|
||||
if diff := cmp.Diff(buffer.View(expectedPayload), gotPayload); diff != "" {
|
||||
if diff := cmp.Diff(tcpip.ReadResult{
|
||||
Count: len(expectedPayload),
|
||||
Total: len(expectedPayload),
|
||||
}, result, checker.IgnoreCmpPath("ControlMessages")); diff != "" {
|
||||
t.Errorf("(i=%d) ep.Read: unexpected result (-want +got):\n%s", i, diff)
|
||||
}
|
||||
if diff := cmp.Diff(expectedPayload, buf.Bytes()); diff != "" {
|
||||
t.Errorf("(i=%d) got UDP payload mismatch (-want +got):\n%s", i, diff)
|
||||
}
|
||||
}
|
||||
|
||||
if gotPayload, _, err := ep.Read(nil); err != tcpip.ErrWouldBlock {
|
||||
t.Fatalf("(last) got Read(nil) = (%x, _, %v), want = (_, _, %s)", gotPayload, err, tcpip.ErrWouldBlock)
|
||||
if res, err := ep.Read(ioutil.Discard, rcvSize, tcpip.ReadOptions{}); err != tcpip.ErrWouldBlock {
|
||||
t.Fatalf("(last) got Read = (%v, %v), want = (_, %s)", res, err, tcpip.ErrWouldBlock)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15,8 +15,10 @@
|
||||
package ipv6
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"net"
|
||||
"testing"
|
||||
@@ -844,13 +846,14 @@ func TestReceiveIPv6ExtHdrs(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
const mtu = header.IPv6MinimumMTU
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
s := stack.New(stack.Options{
|
||||
NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
|
||||
TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
|
||||
})
|
||||
e := channel.New(1, header.IPv6MinimumMTU, linkAddr1)
|
||||
e := channel.New(1, mtu, linkAddr1)
|
||||
if err := s.CreateNIC(nicID, e); err != nil {
|
||||
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
|
||||
}
|
||||
@@ -979,17 +982,24 @@ func TestReceiveIPv6ExtHdrs(t *testing.T) {
|
||||
if got := stats.Value(); got != 1 {
|
||||
t.Errorf("got UDP Rx Packets = %d, want = 1", got)
|
||||
}
|
||||
gotPayload, _, err := ep.Read(nil)
|
||||
var buf bytes.Buffer
|
||||
result, err := ep.Read(&buf, mtu, tcpip.ReadOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Read(nil): %s", err)
|
||||
t.Fatalf("Read: %s", err)
|
||||
}
|
||||
if diff := cmp.Diff(buffer.View(udpPayload), gotPayload); diff != "" {
|
||||
if diff := cmp.Diff(tcpip.ReadResult{
|
||||
Count: len(udpPayload),
|
||||
Total: len(udpPayload),
|
||||
}, result, checker.IgnoreCmpPath("ControlMessages")); diff != "" {
|
||||
t.Errorf("Read: unexpected result (-want +got):\n%s", diff)
|
||||
}
|
||||
if diff := cmp.Diff(udpPayload, buf.Bytes()); diff != "" {
|
||||
t.Errorf("got UDP payload mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
|
||||
// Should not have any more UDP packets.
|
||||
if gotPayload, _, err := ep.Read(nil); err != tcpip.ErrWouldBlock {
|
||||
t.Fatalf("got Read(nil) = (%x, _, %v), want = (_, _, %s)", gotPayload, err, tcpip.ErrWouldBlock)
|
||||
if res, err := ep.Read(ioutil.Discard, mtu, tcpip.ReadOptions{}); err != tcpip.ErrWouldBlock {
|
||||
t.Fatalf("got Read = (%v, %v), want = (_, %s)", res, err, tcpip.ErrWouldBlock)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1969,18 +1979,20 @@ func TestReceiveIPv6Fragments(t *testing.T) {
|
||||
t.Errorf("got UDP Rx Packets = %d, want = %d", got, want)
|
||||
}
|
||||
|
||||
const rcvSize = 65536 // Account for reassembled packets.
|
||||
for i, p := range test.expectedPayloads {
|
||||
gotPayload, _, err := ep.Read(nil)
|
||||
var buf bytes.Buffer
|
||||
_, err := ep.Read(&buf, rcvSize, tcpip.ReadOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("(i=%d) Read(nil): %s", i, err)
|
||||
t.Fatalf("(i=%d) Read: %s", i, err)
|
||||
}
|
||||
if diff := cmp.Diff(buffer.View(p), gotPayload); diff != "" {
|
||||
if diff := cmp.Diff(p, buf.Bytes()); diff != "" {
|
||||
t.Errorf("(i=%d) got UDP payload mismatch (-want +got):\n%s", i, diff)
|
||||
}
|
||||
}
|
||||
|
||||
if gotPayload, _, err := ep.Read(nil); err != tcpip.ErrWouldBlock {
|
||||
t.Fatalf("(last) got Read(nil) = (%x, _, %v), want = (_, _, %s)", gotPayload, err, tcpip.ErrWouldBlock)
|
||||
if res, err := ep.Read(ioutil.Discard, rcvSize, tcpip.ReadOptions{}); err != tcpip.ErrWouldBlock {
|
||||
t.Fatalf("(last) got Read = (%v, %v), want = (_, %s)", res, err, tcpip.ErrWouldBlock)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
@@ -200,7 +201,7 @@ func main() {
|
||||
// connection from its side.
|
||||
wq.EventRegister(&waitEntry, waiter.EventIn)
|
||||
for {
|
||||
v, _, err := ep.Read(nil)
|
||||
_, err := ep.Read(os.Stdout, math.MaxUint16, tcpip.ReadOptions{})
|
||||
if err != nil {
|
||||
if err == tcpip.ErrClosedForReceive {
|
||||
break
|
||||
@@ -213,8 +214,6 @@ func main() {
|
||||
|
||||
log.Fatal("Read() failed:", err)
|
||||
}
|
||||
|
||||
os.Stdout.Write(v)
|
||||
}
|
||||
wq.EventUnregister(&waitEntry)
|
||||
|
||||
|
||||
@@ -20,8 +20,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"log"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
@@ -54,7 +56,8 @@ func echo(wq *waiter.Queue, ep tcpip.Endpoint) {
|
||||
defer wq.EventUnregister(&waitEntry)
|
||||
|
||||
for {
|
||||
v, _, err := ep.Read(nil)
|
||||
var buf bytes.Buffer
|
||||
_, err := ep.Read(&buf, math.MaxUint16, tcpip.ReadOptions{})
|
||||
if err != nil {
|
||||
if err == tcpip.ErrWouldBlock {
|
||||
<-notifyCh
|
||||
@@ -64,7 +67,7 @@ func echo(wq *waiter.Queue, ep tcpip.Endpoint) {
|
||||
return
|
||||
}
|
||||
|
||||
ep.Write(tcpip.SlicePayload(v), tcpip.WriteOptions{})
|
||||
ep.Write(tcpip.SlicePayload(buf.Bytes()), tcpip.WriteOptions{})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package stack_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"math/rand"
|
||||
"testing"
|
||||
@@ -351,7 +352,7 @@ func TestBindToDeviceDistribution(t *testing.T) {
|
||||
}
|
||||
|
||||
ep := <-pollChannel
|
||||
if _, _, err := ep.Read(nil); err != nil {
|
||||
if _, err := ep.Read(ioutil.Discard, math.MaxUint16, tcpip.ReadOptions{}); err != nil {
|
||||
t.Fatalf("Read on endpoint %d failed: %s", eps[ep], err)
|
||||
}
|
||||
stats[ep]++
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package stack_test
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
@@ -85,8 +86,8 @@ func (*fakeTransportEndpoint) Readiness(mask waiter.EventMask) waiter.EventMask
|
||||
return mask
|
||||
}
|
||||
|
||||
func (*fakeTransportEndpoint) Read(*tcpip.FullAddress) (buffer.View, tcpip.ControlMessages, *tcpip.Error) {
|
||||
return buffer.View{}, tcpip.ControlMessages{}, nil
|
||||
func (*fakeTransportEndpoint) Read(io.Writer, int, tcpip.ReadOptions) (tcpip.ReadResult, *tcpip.Error) {
|
||||
return tcpip.ReadResult{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeTransportEndpoint) Write(p tcpip.Payloader, opts tcpip.WriteOptions) (int64, <-chan struct{}, *tcpip.Error) {
|
||||
@@ -110,10 +111,6 @@ func (f *fakeTransportEndpoint) Write(p tcpip.Payloader, opts tcpip.WriteOptions
|
||||
return int64(len(v)), nil, nil
|
||||
}
|
||||
|
||||
func (*fakeTransportEndpoint) Peek([][]byte) (int64, *tcpip.Error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// SetSockOpt sets a socket option. Currently not supported.
|
||||
func (*fakeTransportEndpoint) SetSockOpt(tcpip.SettableSocketOption) *tcpip.Error {
|
||||
return tcpip.ErrInvalidEndpointState
|
||||
|
||||
+60
-21
@@ -31,6 +31,7 @@ package tcpip
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/bits"
|
||||
"reflect"
|
||||
"strconv"
|
||||
@@ -39,7 +40,6 @@ import (
|
||||
"time"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/sync"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
||||
"gvisor.dev/gvisor/pkg/waiter"
|
||||
)
|
||||
|
||||
@@ -113,6 +113,7 @@ var (
|
||||
ErrNotPermitted = &Error{msg: "operation not permitted"}
|
||||
ErrAddressFamilyNotSupported = &Error{msg: "address family not supported by protocol"}
|
||||
ErrMalformedHeader = &Error{msg: "header is malformed"}
|
||||
ErrBadBuffer = &Error{msg: "bad buffer"}
|
||||
)
|
||||
|
||||
var messageToError map[string]*Error
|
||||
@@ -162,6 +163,7 @@ func StringToError(s string) *Error {
|
||||
ErrNotPermitted,
|
||||
ErrAddressFamilyNotSupported,
|
||||
ErrMalformedHeader,
|
||||
ErrBadBuffer,
|
||||
}
|
||||
|
||||
messageToError = make(map[string]*Error)
|
||||
@@ -496,6 +498,21 @@ func (s SlicePayload) Payload(size int) ([]byte, *Error) {
|
||||
return s[:size], nil
|
||||
}
|
||||
|
||||
var _ io.Writer = (*SliceWriter)(nil)
|
||||
|
||||
// SliceWriter implements io.Writer for slices.
|
||||
type SliceWriter []byte
|
||||
|
||||
// Write implements io.Writer.Write.
|
||||
func (s *SliceWriter) Write(b []byte) (int, error) {
|
||||
n := copy(*s, b)
|
||||
*s = (*s)[n:]
|
||||
if n < len(b) {
|
||||
return n, io.ErrShortWrite
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// A ControlMessages contains socket control messages for IP sockets.
|
||||
//
|
||||
// +stateify savable
|
||||
@@ -552,6 +569,40 @@ type PacketOwner interface {
|
||||
GID() uint32
|
||||
}
|
||||
|
||||
// ReadOptions contains options for Endpoint.Read.
|
||||
type ReadOptions struct {
|
||||
// Peek indicates whether this read is a peek.
|
||||
Peek bool
|
||||
|
||||
// NeedRemoteAddr indicates whether to return the remote address, if
|
||||
// supported.
|
||||
NeedRemoteAddr bool
|
||||
|
||||
// NeedLinkPacketInfo indicates whether to return the link-layer information,
|
||||
// if supported.
|
||||
NeedLinkPacketInfo bool
|
||||
}
|
||||
|
||||
// ReadResult represents result for a successful Endpoint.Read.
|
||||
type ReadResult struct {
|
||||
// Count is the number of bytes received and written to the buffer.
|
||||
Count int
|
||||
|
||||
// Total is the number of bytes of the received packet. This can be used to
|
||||
// determine whether the read is truncated.
|
||||
Total int
|
||||
|
||||
// ControlMessages is the control messages received.
|
||||
ControlMessages ControlMessages
|
||||
|
||||
// RemoteAddr is the remote address if ReadOptions.NeedAddr is true.
|
||||
RemoteAddr FullAddress
|
||||
|
||||
// LinkPacketInfo is the link-layer information of the received packet if
|
||||
// ReadOptions.NeedLinkPacketInfo is true.
|
||||
LinkPacketInfo LinkPacketInfo
|
||||
}
|
||||
|
||||
// Endpoint is the interface implemented by transport protocols (e.g., tcp, udp)
|
||||
// that exposes functionality like read, write, connect, etc. to users of the
|
||||
// networking stack.
|
||||
@@ -566,11 +617,15 @@ type Endpoint interface {
|
||||
// Abort is best effort; implementing Abort with Close is acceptable.
|
||||
Abort()
|
||||
|
||||
// Read reads data from the endpoint and optionally returns the sender.
|
||||
// Read reads data from the endpoint and optionally writes to dst.
|
||||
//
|
||||
// This method does not block if there is no data pending. It will also
|
||||
// either return an error or data, never both.
|
||||
Read(*FullAddress) (buffer.View, ControlMessages, *Error)
|
||||
// This method does not block if there is no data pending; in this case,
|
||||
// ErrWouldBlock is returned.
|
||||
//
|
||||
// If non-zero number of bytes are successfully read and written to dst, err
|
||||
// must be nil. Otherwise, if dst failed to write anything, ErrBadBuffer
|
||||
// should be returned.
|
||||
Read(dst io.Writer, count int, opts ReadOptions) (res ReadResult, err *Error)
|
||||
|
||||
// Write writes data to the endpoint's peer. This method does not block if
|
||||
// the data cannot be written.
|
||||
@@ -592,11 +647,6 @@ type Endpoint interface {
|
||||
// not). The channel is only non-nil in this case.
|
||||
Write(Payloader, WriteOptions) (int64, <-chan struct{}, *Error)
|
||||
|
||||
// Peek reads data without consuming it from the endpoint.
|
||||
//
|
||||
// This method does not block if there is no data pending.
|
||||
Peek([][]byte) (int64, *Error)
|
||||
|
||||
// Connect connects the endpoint to its peer. Specifying a NIC is
|
||||
// optional.
|
||||
//
|
||||
@@ -703,17 +753,6 @@ type LinkPacketInfo struct {
|
||||
PktType PacketType
|
||||
}
|
||||
|
||||
// PacketEndpoint are additional methods that are only implemented by Packet
|
||||
// endpoints.
|
||||
type PacketEndpoint interface {
|
||||
// ReadPacket reads a datagram/packet from the endpoint and optionally
|
||||
// returns the sender and additional LinkPacketInfo.
|
||||
//
|
||||
// This method does not block if there is no data pending. It will also
|
||||
// either return an error or data, never both.
|
||||
ReadPacket(*FullAddress, *LinkPacketInfo) (buffer.View, ControlMessages, *Error)
|
||||
}
|
||||
|
||||
// EndpointInfo is the interface implemented by each endpoint info struct.
|
||||
type EndpointInfo interface {
|
||||
// IsEndpointInfo is an empty method to implement the tcpip.EndpointInfo
|
||||
|
||||
@@ -15,6 +15,7 @@ go_test(
|
||||
deps = [
|
||||
"//pkg/tcpip",
|
||||
"//pkg/tcpip/buffer",
|
||||
"//pkg/tcpip/checker",
|
||||
"//pkg/tcpip/header",
|
||||
"//pkg/tcpip/link/channel",
|
||||
"//pkg/tcpip/link/ethernet",
|
||||
|
||||
@@ -15,12 +15,13 @@
|
||||
package integration_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/checker"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/ethernet"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/nested"
|
||||
@@ -382,24 +383,33 @@ func TestForwarding(t *testing.T) {
|
||||
|
||||
// Wait for the endpoint to be readable.
|
||||
<-ch
|
||||
var addr tcpip.FullAddress
|
||||
v, _, err := ep.Read(&addr)
|
||||
var buf bytes.Buffer
|
||||
opts := tcpip.ReadOptions{NeedRemoteAddr: true}
|
||||
res, err := ep.Read(&buf, len(data), opts)
|
||||
if err != nil {
|
||||
t.Fatalf("ep.Read(_): %s", err)
|
||||
t.Fatalf("ep.Read(_, %d, %#v): %s", len(data), opts, err)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(v, buffer.View(data)); diff != "" {
|
||||
t.Errorf("received data mismatch (-want +got):\n%s", diff)
|
||||
if diff := cmp.Diff(tcpip.ReadResult{
|
||||
Count: len(data),
|
||||
Total: len(data),
|
||||
RemoteAddr: tcpip.FullAddress{Addr: expectedFrom},
|
||||
}, res, checker.IgnoreCmpPath(
|
||||
"ControlMessages",
|
||||
"RemoteAddr.NIC",
|
||||
"RemoteAddr.Port",
|
||||
)); diff != "" {
|
||||
t.Errorf("ep.Read: unexpected result (-want +got):\n%s", diff)
|
||||
}
|
||||
if addr.Addr != expectedFrom {
|
||||
t.Errorf("got addr.Addr = %s, want = %s", addr.Addr, expectedFrom)
|
||||
if diff := cmp.Diff(buf.Bytes(), data); diff != "" {
|
||||
t.Errorf("received data mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
|
||||
if t.Failed() {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
return addr
|
||||
return res.RemoteAddr
|
||||
}
|
||||
|
||||
addr := read(epsAndAddrs.serverReadableCH, epsAndAddrs.serverEP, data, epsAndAddrs.clientAddr)
|
||||
|
||||
@@ -15,12 +15,13 @@
|
||||
package integration_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/checker"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/pipe"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/network/arp"
|
||||
@@ -86,21 +87,21 @@ func TestPing(t *testing.T) {
|
||||
transProto tcpip.TransportProtocolNumber
|
||||
netProto tcpip.NetworkProtocolNumber
|
||||
remoteAddr tcpip.Address
|
||||
icmpBuf func(*testing.T) buffer.View
|
||||
icmpBuf func(*testing.T) []byte
|
||||
}{
|
||||
{
|
||||
name: "IPv4 Ping",
|
||||
transProto: icmp.ProtocolNumber4,
|
||||
netProto: ipv4.ProtocolNumber,
|
||||
remoteAddr: ipv4Addr2.AddressWithPrefix.Address,
|
||||
icmpBuf: func(t *testing.T) buffer.View {
|
||||
icmpBuf: func(t *testing.T) []byte {
|
||||
data := [8]byte{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
hdr := header.ICMPv4(make([]byte, header.ICMPv4MinimumSize+len(data)))
|
||||
hdr.SetType(header.ICMPv4Echo)
|
||||
if n := copy(hdr.Payload(), data[:]); n != len(data) {
|
||||
t.Fatalf("copied %d bytes but expected to copy %d bytes", n, len(data))
|
||||
}
|
||||
return buffer.View(hdr)
|
||||
return hdr
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -108,14 +109,14 @@ func TestPing(t *testing.T) {
|
||||
transProto: icmp.ProtocolNumber6,
|
||||
netProto: ipv6.ProtocolNumber,
|
||||
remoteAddr: ipv6Addr2.AddressWithPrefix.Address,
|
||||
icmpBuf: func(t *testing.T) buffer.View {
|
||||
icmpBuf: func(t *testing.T) []byte {
|
||||
data := [8]byte{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
hdr := header.ICMPv6(make([]byte, header.ICMPv6MinimumSize+len(data)))
|
||||
hdr.SetType(header.ICMPv6EchoRequest)
|
||||
if n := copy(hdr.Payload(), data[:]); n != len(data) {
|
||||
t.Fatalf("copied %d bytes but expected to copy %d bytes", n, len(data))
|
||||
}
|
||||
return buffer.View(hdr)
|
||||
return hdr
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -200,17 +201,26 @@ func TestPing(t *testing.T) {
|
||||
// Wait for the endpoint to be readable.
|
||||
<-waiterCH
|
||||
|
||||
var addr tcpip.FullAddress
|
||||
v, _, err := ep.Read(&addr)
|
||||
var buf bytes.Buffer
|
||||
opts := tcpip.ReadOptions{NeedRemoteAddr: true}
|
||||
res, err := ep.Read(&buf, len(icmpBuf), opts)
|
||||
if err != nil {
|
||||
t.Fatalf("ep.Read(_): %s", err)
|
||||
t.Fatalf("ep.Read(_, %d, %#v): %s", len(icmpBuf), opts, err)
|
||||
}
|
||||
if diff := cmp.Diff(v[icmpDataOffset:], icmpBuf[icmpDataOffset:]); diff != "" {
|
||||
if diff := cmp.Diff(tcpip.ReadResult{
|
||||
Count: buf.Len(),
|
||||
Total: buf.Len(),
|
||||
RemoteAddr: tcpip.FullAddress{Addr: test.remoteAddr},
|
||||
}, res, checker.IgnoreCmpPath(
|
||||
"ControlMessages",
|
||||
"RemoteAddr.NIC",
|
||||
"RemoteAddr.Port",
|
||||
)); diff != "" {
|
||||
t.Errorf("ep.Read: unexpected result (-want +got):\n%s", diff)
|
||||
}
|
||||
if diff := cmp.Diff(buf.Bytes()[icmpDataOffset:], icmpBuf[icmpDataOffset:]); diff != "" {
|
||||
t.Errorf("received data mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
if addr.Addr != test.remoteAddr {
|
||||
t.Errorf("got addr.Addr = %s, want = %s", addr.Addr, test.remoteAddr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,12 +15,14 @@
|
||||
package integration_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/checker"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/loopback"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
||||
@@ -238,21 +240,28 @@ func TestLoopbackAcceptAllInSubnetUDP(t *testing.T) {
|
||||
t.Fatalf("got sep.Write(_, _) = (%d, _, nil), want = (%d, _, nil)", n, want)
|
||||
}
|
||||
|
||||
var addr tcpip.FullAddress
|
||||
if gotPayload, _, err := rep.Read(&addr); test.expectRx {
|
||||
var buf bytes.Buffer
|
||||
opts := tcpip.ReadOptions{NeedRemoteAddr: true}
|
||||
if res, err := rep.Read(&buf, len(data), opts); test.expectRx {
|
||||
if err != nil {
|
||||
t.Fatalf("reep.Read(_): %s", err)
|
||||
t.Fatalf("rep.Read(_, %d, %#v): %s", len(data), opts, err)
|
||||
}
|
||||
if diff := cmp.Diff(buffer.View(data), gotPayload); diff != "" {
|
||||
if diff := cmp.Diff(tcpip.ReadResult{
|
||||
Count: buf.Len(),
|
||||
Total: buf.Len(),
|
||||
RemoteAddr: tcpip.FullAddress{
|
||||
Addr: test.addAddress.AddressWithPrefix.Address,
|
||||
},
|
||||
}, res,
|
||||
checker.IgnoreCmpPath("ControlMessages", "RemoteAddr.NIC", "RemoteAddr.Port"),
|
||||
); diff != "" {
|
||||
t.Errorf("rep.Read: unexpected result (-want +got):\n%s", diff)
|
||||
}
|
||||
if diff := cmp.Diff(data, buf.Bytes()); diff != "" {
|
||||
t.Errorf("got UDP payload mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
if addr.Addr != test.addAddress.AddressWithPrefix.Address {
|
||||
t.Errorf("got addr.Addr = %s, want = %s", addr.Addr, test.addAddress.AddressWithPrefix.Address)
|
||||
}
|
||||
} else {
|
||||
if err != tcpip.ErrWouldBlock {
|
||||
t.Fatalf("got rep.Read(nil) = (%x, _, %s), want = (_, _, %s)", gotPayload, err, tcpip.ErrWouldBlock)
|
||||
}
|
||||
} else if err != tcpip.ErrWouldBlock {
|
||||
t.Fatalf("got rep.Read = (%v, %s) [with data %x], want = (_, %s)", res, err, buf.Bytes(), tcpip.ErrWouldBlock)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15,12 +15,14 @@
|
||||
package integration_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/checker"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/loopback"
|
||||
@@ -462,17 +464,23 @@ func TestIncomingMulticastAndBroadcast(t *testing.T) {
|
||||
}
|
||||
|
||||
test.rxUDP(e, test.remoteAddr, test.dstAddr, data)
|
||||
if gotPayload, _, err := ep.Read(nil); test.expectRx {
|
||||
var buf bytes.Buffer
|
||||
var opts tcpip.ReadOptions
|
||||
if res, err := ep.Read(&buf, len(data), opts); test.expectRx {
|
||||
if err != nil {
|
||||
t.Fatalf("Read(nil): %s", err)
|
||||
t.Fatalf("ep.Read(_, %d, %#v): %s", len(data), opts, err)
|
||||
}
|
||||
if diff := cmp.Diff(buffer.View(data), gotPayload); diff != "" {
|
||||
if diff := cmp.Diff(tcpip.ReadResult{
|
||||
Count: buf.Len(),
|
||||
Total: buf.Len(),
|
||||
}, res, checker.IgnoreCmpPath("ControlMessages")); diff != "" {
|
||||
t.Errorf("ep.Read: unexpected result (-want +got):\n%s", diff)
|
||||
}
|
||||
if diff := cmp.Diff(data, buf.Bytes()); diff != "" {
|
||||
t.Errorf("got UDP payload mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
} else {
|
||||
if err != tcpip.ErrWouldBlock {
|
||||
t.Fatalf("got Read(nil) = (%x, _, %s), want = (_, _, %s)", gotPayload, err, tcpip.ErrWouldBlock)
|
||||
}
|
||||
} else if err != tcpip.ErrWouldBlock {
|
||||
t.Fatalf("got Read = (%v, %s) [with data %x], want = (_, %s)", res, err, buf.Bytes(), tcpip.ErrWouldBlock)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -589,9 +597,19 @@ func TestReuseAddrAndBroadcast(t *testing.T) {
|
||||
// Wait for the endpoint to become readable.
|
||||
<-rep.ch
|
||||
|
||||
if gotPayload, _, err := rep.ep.Read(nil); err != nil {
|
||||
t.Errorf("(eps[%d] write) eps[%d].Read(nil): %s", i, j, err)
|
||||
} else if diff := cmp.Diff(buffer.View(data), gotPayload); diff != "" {
|
||||
var buf bytes.Buffer
|
||||
result, err := rep.ep.Read(&buf, len(data), tcpip.ReadOptions{})
|
||||
if err != nil {
|
||||
t.Errorf("(eps[%d] write) eps[%d].Read: %s", i, j, err)
|
||||
continue
|
||||
}
|
||||
if diff := cmp.Diff(tcpip.ReadResult{
|
||||
Count: buf.Len(),
|
||||
Total: buf.Len(),
|
||||
}, result, checker.IgnoreCmpPath("ControlMessages")); diff != "" {
|
||||
t.Errorf("(eps[%d] write) eps[%d].Read: unexpected result (-want +got):\n%s", i, j, diff)
|
||||
}
|
||||
if diff := cmp.Diff([]byte(data), buf.Bytes()); diff != "" {
|
||||
t.Errorf("(eps[%d] write) got UDP payload from eps[%d] mismatch (-want +got):\n%s", i, j, diff)
|
||||
}
|
||||
}
|
||||
@@ -719,10 +737,20 @@ func TestUDPAddRemoveMembershipSocketOption(t *testing.T) {
|
||||
t.Fatalf("ep.SetSockOpt(&%#v): %s", addOpt, err)
|
||||
}
|
||||
test.rxUDP(e, test.remoteAddr, test.multicastAddr, data)
|
||||
if gotPayload, _, err := ep.Read(nil); err != nil {
|
||||
t.Fatalf("ep.Read(nil): %s", err)
|
||||
} else if diff := cmp.Diff(buffer.View(data), gotPayload); diff != "" {
|
||||
t.Errorf("got UDP payload mismatch (-want +got):\n%s", diff)
|
||||
var buf bytes.Buffer
|
||||
result, err := ep.Read(&buf, len(data), tcpip.ReadOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("ep.Read: %s", err)
|
||||
} else {
|
||||
if diff := cmp.Diff(tcpip.ReadResult{
|
||||
Count: buf.Len(),
|
||||
Total: buf.Len(),
|
||||
}, result, checker.IgnoreCmpPath("ControlMessages")); diff != "" {
|
||||
t.Errorf("ep.Read: unexpected result (-want +got):\n%s", diff)
|
||||
}
|
||||
if diff := cmp.Diff(data, buf.Bytes()); diff != "" {
|
||||
t.Errorf("got UDP payload mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
// We should not receive UDP packets to the group once we leave
|
||||
@@ -731,8 +759,8 @@ func TestUDPAddRemoveMembershipSocketOption(t *testing.T) {
|
||||
if err := ep.SetSockOpt(&removeOpt); err != nil {
|
||||
t.Fatalf("ep.SetSockOpt(&%#v): %s", removeOpt, err)
|
||||
}
|
||||
if gotPayload, _, err := ep.Read(nil); err != tcpip.ErrWouldBlock {
|
||||
t.Fatalf("got ep.Read(nil) = (%x, _, %s), want = (nil, _, %s)", gotPayload, err, tcpip.ErrWouldBlock)
|
||||
if _, err := ep.Read(&buf, 1, tcpip.ReadOptions{}); err != tcpip.ErrWouldBlock {
|
||||
t.Fatalf("got ep.Read = (_, %s), want = (_, %s)", err, tcpip.ErrWouldBlock)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15,11 +15,14 @@
|
||||
package integration_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/checker"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/loopback"
|
||||
@@ -203,17 +206,26 @@ func TestLocalPing(t *testing.T) {
|
||||
// Wait for the endpoint to become readable.
|
||||
<-ch
|
||||
|
||||
var addr tcpip.FullAddress
|
||||
v, _, err := ep.Read(&addr)
|
||||
var buf bytes.Buffer
|
||||
opts := tcpip.ReadOptions{NeedRemoteAddr: true}
|
||||
res, err := ep.Read(&buf, math.MaxUint16, opts)
|
||||
if err != nil {
|
||||
t.Fatalf("ep.Read(_): %s", err)
|
||||
t.Fatalf("ep.Read(_, %d, %#v): %s", math.MaxUint16, opts, err)
|
||||
}
|
||||
if diff := cmp.Diff(v[icmpDataOffset:], buffer.View(payload[icmpDataOffset:])); diff != "" {
|
||||
if diff := cmp.Diff(tcpip.ReadResult{
|
||||
Count: buf.Len(),
|
||||
Total: buf.Len(),
|
||||
RemoteAddr: tcpip.FullAddress{Addr: test.localAddr},
|
||||
}, res, checker.IgnoreCmpPath(
|
||||
"ControlMessages",
|
||||
"RemoteAddr.NIC",
|
||||
"RemoteAddr.Port",
|
||||
)); diff != "" {
|
||||
t.Errorf("ep.Read: unexpected result (-want +got):\n%s", diff)
|
||||
}
|
||||
if diff := cmp.Diff(buf.Bytes()[icmpDataOffset:], []byte(payload[icmpDataOffset:])); diff != "" {
|
||||
t.Errorf("received data mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
if addr.Addr != test.localAddr {
|
||||
t.Errorf("got addr.Addr = %s, want = %s", addr.Addr, test.localAddr)
|
||||
}
|
||||
|
||||
test.checkLinkEndpoint(t, e)
|
||||
})
|
||||
@@ -338,14 +350,27 @@ func TestLocalUDP(t *testing.T) {
|
||||
<-serverCH
|
||||
|
||||
var clientAddr tcpip.FullAddress
|
||||
if v, _, err := server.Read(&clientAddr); err != nil {
|
||||
var readBuf bytes.Buffer
|
||||
if read, err := server.Read(&readBuf, math.MaxUint16, tcpip.ReadOptions{NeedRemoteAddr: true}); err != nil {
|
||||
t.Fatalf("server.Read(_): %s", err)
|
||||
} else {
|
||||
if diff := cmp.Diff(buffer.View(clientPayload), v); diff != "" {
|
||||
t.Errorf("server read clientPayload mismatch (-want +got):\n%s", diff)
|
||||
clientAddr = read.RemoteAddr
|
||||
|
||||
if diff := cmp.Diff(tcpip.ReadResult{
|
||||
Count: readBuf.Len(),
|
||||
Total: readBuf.Len(),
|
||||
RemoteAddr: tcpip.FullAddress{
|
||||
Addr: test.canBePrimaryAddr.AddressWithPrefix.Address,
|
||||
},
|
||||
}, read, checker.IgnoreCmpPath(
|
||||
"ControlMessages",
|
||||
"RemoteAddr.NIC",
|
||||
"RemoteAddr.Port",
|
||||
)); diff != "" {
|
||||
t.Errorf("server.Read: unexpected result (-want +got):\n%s", diff)
|
||||
}
|
||||
if clientAddr.Addr != test.canBePrimaryAddr.AddressWithPrefix.Address {
|
||||
t.Errorf("got clientAddr.Addr = %s, want = %s", clientAddr.Addr, test.canBePrimaryAddr.AddressWithPrefix.Address)
|
||||
if diff := cmp.Diff(buffer.View(clientPayload), buffer.View(readBuf.Bytes())); diff != "" {
|
||||
t.Errorf("server read clientPayload mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
if t.Failed() {
|
||||
t.FailNow()
|
||||
@@ -367,15 +392,23 @@ func TestLocalUDP(t *testing.T) {
|
||||
// Wait for the client endpoint to become readable.
|
||||
<-clientCH
|
||||
|
||||
var gotServerAddr tcpip.FullAddress
|
||||
if v, _, err := client.Read(&gotServerAddr); err != nil {
|
||||
readBuf.Reset()
|
||||
if read, err := client.Read(&readBuf, math.MaxUint16, tcpip.ReadOptions{NeedRemoteAddr: true}); err != nil {
|
||||
t.Fatalf("client.Read(_): %s", err)
|
||||
} else {
|
||||
if diff := cmp.Diff(buffer.View(serverPayload), v); diff != "" {
|
||||
t.Errorf("client read serverPayload mismatch (-want +got):\n%s", diff)
|
||||
if diff := cmp.Diff(tcpip.ReadResult{
|
||||
Count: readBuf.Len(),
|
||||
Total: readBuf.Len(),
|
||||
RemoteAddr: tcpip.FullAddress{Addr: serverAddr.Addr},
|
||||
}, read, checker.IgnoreCmpPath(
|
||||
"ControlMessages",
|
||||
"RemoteAddr.NIC",
|
||||
"RemoteAddr.Port",
|
||||
)); diff != "" {
|
||||
t.Errorf("client.Read: unexpected result (-want +got):\n%s", diff)
|
||||
}
|
||||
if gotServerAddr.Addr != serverAddr.Addr {
|
||||
t.Errorf("got gotServerAddr.Addr = %s, want = %s", gotServerAddr.Addr, serverAddr.Addr)
|
||||
if diff := cmp.Diff(buffer.View(serverPayload), buffer.View(readBuf.Bytes())); diff != "" {
|
||||
t.Errorf("client read serverPayload mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
if t.Failed() {
|
||||
t.FailNow()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user