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:
Ting-Yu Wang
2021-01-07 14:17:18 -08:00
committed by gVisor bot
parent f4b4ed666d
commit b1de1da318
35 changed files with 886 additions and 731 deletions
-1
View File
@@ -25,7 +25,6 @@ go_library(
"//pkg/marshal",
"//pkg/marshal/primitive",
"//pkg/metric",
"//pkg/safemem",
"//pkg/sentry/arch",
"//pkg/sentry/device",
"//pkg/sentry/fs",
+77 -188
View File
@@ -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
}
+2
View File
@@ -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
+19 -38
View File
@@ -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) {
+33 -4
View File
@@ -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 {
+59 -9
View File
@@ -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
+12
View File
@@ -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 -5
View File
@@ -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)
}
})
}
+23 -11
View File
@@ -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)
}
})
}
+2 -3
View File
@@ -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)
+5 -2
View File
@@ -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{})
}
}
+2 -1
View File
@@ -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]++
+3 -6
View File
@@ -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
View File
@@ -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
+1
View File
@@ -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",
+19 -9
View File
@@ -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)
}
})
}
}
+20 -11
View File
@@ -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)
}
})
}
+51 -18
View File
@@ -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