Trigger poll/epoll events on zero-length hostinet sendmsg

Fixes #2726

PiperOrigin-RevId: 380753516
This commit is contained in:
Ian Lewis
2021-06-22 01:10:24 -07:00
committed by gVisor bot
parent c6da1b0022
commit 04a81bc336
2 changed files with 103 additions and 96 deletions
+101 -90
View File
@@ -67,23 +67,6 @@ type socketOperations struct {
socketOpsCommon
}
// socketOpsCommon contains the socket operations common to VFS1 and VFS2.
//
// +stateify savable
type socketOpsCommon struct {
socket.SendReceiveTimeout
family int // Read-only.
stype linux.SockType // Read-only.
protocol int // Read-only.
queue waiter.Queue
// fd is the host socket fd. It must have O_NONBLOCK, so that operations
// will return EWOULDBLOCK instead of blocking on the host. This allows us to
// handle blocking behavior independently in the sentry.
fd int
}
var _ = socket.Socket(&socketOperations{})
func newSocketFile(ctx context.Context, family int, stype linux.SockType, protocol int, fd int, nonblock bool) (*fs.File, *syserr.Error) {
@@ -103,29 +86,6 @@ func newSocketFile(ctx context.Context, family int, stype linux.SockType, protoc
return fs.NewFile(ctx, dirent, fs.FileFlags{NonBlocking: nonblock, Read: true, Write: true, NonSeekable: true}, s), nil
}
// Release implements fs.FileOperations.Release.
func (s *socketOpsCommon) Release(context.Context) {
fdnotifier.RemoveFD(int32(s.fd))
unix.Close(s.fd)
}
// Readiness implements waiter.Waitable.Readiness.
func (s *socketOpsCommon) Readiness(mask waiter.EventMask) waiter.EventMask {
return fdnotifier.NonBlockingPoll(int32(s.fd), mask)
}
// EventRegister implements waiter.Waitable.EventRegister.
func (s *socketOpsCommon) EventRegister(e *waiter.Entry, mask waiter.EventMask) {
s.queue.EventRegister(e, mask)
fdnotifier.UpdateFD(int32(s.fd))
}
// EventUnregister implements waiter.Waitable.EventUnregister.
func (s *socketOpsCommon) EventUnregister(e *waiter.Entry) {
s.queue.EventUnregister(e)
fdnotifier.UpdateFD(int32(s.fd))
}
// Ioctl implements fs.FileOperations.Ioctl.
func (s *socketOperations) Ioctl(ctx context.Context, _ *fs.File, io usermem.IO, args arch.SyscallArguments) (uintptr, error) {
return ioctl(ctx, s.fd, io, args)
@@ -177,6 +137,96 @@ func (s *socketOperations) Write(ctx context.Context, _ *fs.File, src usermem.IO
return int64(n), err
}
// Socket implements socket.Provider.Socket.
func (p *socketProvider) Socket(t *kernel.Task, stypeflags linux.SockType, protocol int) (*fs.File, *syserr.Error) {
// Check that we are using the host network stack.
stack := t.NetworkContext()
if stack == nil {
return nil, nil
}
if _, ok := stack.(*Stack); !ok {
return nil, nil
}
// Only accept TCP and UDP.
stype := stypeflags & linux.SOCK_TYPE_MASK
switch stype {
case unix.SOCK_STREAM:
switch protocol {
case 0, unix.IPPROTO_TCP:
// ok
default:
return nil, nil
}
case unix.SOCK_DGRAM:
switch protocol {
case 0, unix.IPPROTO_UDP:
// ok
default:
return nil, nil
}
default:
return nil, nil
}
// Conservatively ignore all flags specified by the application and add
// SOCK_NONBLOCK since socketOperations requires it. Pass a protocol of 0
// to simplify the syscall filters, since 0 and IPPROTO_* are equivalent.
fd, err := unix.Socket(p.family, int(stype)|unix.SOCK_NONBLOCK|unix.SOCK_CLOEXEC, 0)
if err != nil {
return nil, syserr.FromError(err)
}
return newSocketFile(t, p.family, stype, protocol, fd, stypeflags&unix.SOCK_NONBLOCK != 0)
}
// Pair implements socket.Provider.Pair.
func (p *socketProvider) Pair(t *kernel.Task, stype linux.SockType, protocol int) (*fs.File, *fs.File, *syserr.Error) {
// Not supported by AF_INET/AF_INET6.
return nil, nil, nil
}
// LINT.ThenChange(./socket_vfs2.go)
// socketOpsCommon contains the socket operations common to VFS1 and VFS2.
//
// +stateify savable
type socketOpsCommon struct {
socket.SendReceiveTimeout
family int // Read-only.
stype linux.SockType // Read-only.
protocol int // Read-only.
queue waiter.Queue
// fd is the host socket fd. It must have O_NONBLOCK, so that operations
// will return EWOULDBLOCK instead of blocking on the host. This allows us to
// handle blocking behavior independently in the sentry.
fd int
}
// Release implements fs.FileOperations.Release.
func (s *socketOpsCommon) Release(context.Context) {
fdnotifier.RemoveFD(int32(s.fd))
unix.Close(s.fd)
}
// Readiness implements waiter.Waitable.Readiness.
func (s *socketOpsCommon) Readiness(mask waiter.EventMask) waiter.EventMask {
return fdnotifier.NonBlockingPoll(int32(s.fd), mask)
}
// EventRegister implements waiter.Waitable.EventRegister.
func (s *socketOpsCommon) EventRegister(e *waiter.Entry, mask waiter.EventMask) {
s.queue.EventRegister(e, mask)
fdnotifier.UpdateFD(int32(s.fd))
}
// EventUnregister implements waiter.Waitable.EventUnregister.
func (s *socketOpsCommon) EventUnregister(e *waiter.Entry) {
s.queue.EventUnregister(e)
fdnotifier.UpdateFD(int32(s.fd))
}
// Connect implements socket.Socket.Connect.
func (s *socketOpsCommon) Connect(t *kernel.Task, sockaddr []byte, blocking bool) *syserr.Error {
if len(sockaddr) > sizeofSockaddr {
@@ -596,6 +646,17 @@ func (s *socketOpsCommon) SendMsg(t *kernel.Task, src usermem.IOSequence, to []b
return 0, syserr.ErrInvalidArgument
}
// If the src is zero-length, call SENDTO directly with a null buffer in
// order to generate poll/epoll notifications.
if src.NumBytes() == 0 {
sysflags := flags | unix.MSG_DONTWAIT
n, _, errno := unix.Syscall6(unix.SYS_SENDTO, uintptr(s.fd), 0, 0, uintptr(sysflags), uintptr(firstBytePtr(to)), uintptr(len(to)))
if errno != 0 {
return 0, syserr.FromError(errno)
}
return int(n), nil
}
space := uint64(control.CmsgsSpace(t, controlMessages))
if space > maxControlLen {
space = maxControlLen
@@ -709,56 +770,6 @@ type socketProvider struct {
family int
}
// Socket implements socket.Provider.Socket.
func (p *socketProvider) Socket(t *kernel.Task, stypeflags linux.SockType, protocol int) (*fs.File, *syserr.Error) {
// Check that we are using the host network stack.
stack := t.NetworkContext()
if stack == nil {
return nil, nil
}
if _, ok := stack.(*Stack); !ok {
return nil, nil
}
// Only accept TCP and UDP.
stype := stypeflags & linux.SOCK_TYPE_MASK
switch stype {
case unix.SOCK_STREAM:
switch protocol {
case 0, unix.IPPROTO_TCP:
// ok
default:
return nil, nil
}
case unix.SOCK_DGRAM:
switch protocol {
case 0, unix.IPPROTO_UDP:
// ok
default:
return nil, nil
}
default:
return nil, nil
}
// Conservatively ignore all flags specified by the application and add
// SOCK_NONBLOCK since socketOperations requires it. Pass a protocol of 0
// to simplify the syscall filters, since 0 and IPPROTO_* are equivalent.
fd, err := unix.Socket(p.family, int(stype)|unix.SOCK_NONBLOCK|unix.SOCK_CLOEXEC, 0)
if err != nil {
return nil, syserr.FromError(err)
}
return newSocketFile(t, p.family, stype, protocol, fd, stypeflags&unix.SOCK_NONBLOCK != 0)
}
// Pair implements socket.Provider.Pair.
func (p *socketProvider) Pair(t *kernel.Task, stype linux.SockType, protocol int) (*fs.File, *fs.File, *syserr.Error) {
// Not supported by AF_INET/AF_INET6.
return nil, nil, nil
}
// LINT.ThenChange(./socket_vfs2.go)
func init() {
for _, family := range []int{unix.AF_INET, unix.AF_INET6} {
socket.RegisterProvider(family, &socketProvider{family})
+2 -6
View File
@@ -1429,12 +1429,8 @@ TEST_P(UdpSocketTest, FIONREADZeroLengthPacket) {
sendto(sock_.get(), buf + i * psize, 0, 0, bind_addr_, addrlen_),
SyscallSucceedsWithValue(0));
// TODO(gvisor.dev/issue/2726): sending a zero-length message to a hostinet
// socket does not cause a poll event to be triggered.
if (!IsRunningWithHostinet()) {
ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout=*/1000),
SyscallSucceedsWithValue(1));
}
ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout=*/1000),
SyscallSucceedsWithValue(1));
// Check that regardless of how many packets are in the queue, the size
// reported is that of a single packet.