mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Shutdown host sockets on internal shutdown
This is required to make the shutdown visible to peers outside the sandbox. The readClosed / writeClosed fields were dropped, as they were preventing a shutdown socket from reading the remainder of queued bytes. The host syscalls will return the appropriate errors for shutdown. The control message tests have been split out of socket_unix.cc to make the (few) remaining tests accessible to testing inherited host UDS, which don't support sending control messages. Updates #273 PiperOrigin-RevId: 251763060
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
package host
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
@@ -51,20 +52,6 @@ type ConnectedEndpoint struct {
|
||||
// ref keeps track of references to a connectedEndpoint.
|
||||
ref refs.AtomicRefCount
|
||||
|
||||
// mu protects fd, readClosed and writeClosed.
|
||||
mu sync.RWMutex `state:"nosave"`
|
||||
|
||||
// file is an *fd.FD containing the FD backing this endpoint. It must be
|
||||
// set to nil if it has been closed.
|
||||
file *fd.FD `state:"nosave"`
|
||||
|
||||
// readClosed is true if the FD has read shutdown or if it has been closed.
|
||||
readClosed bool
|
||||
|
||||
// writeClosed is true if the FD has write shutdown or if it has been
|
||||
// closed.
|
||||
writeClosed bool
|
||||
|
||||
// If srfd >= 0, it is the host FD that file was imported from.
|
||||
srfd int `state:"wait"`
|
||||
|
||||
@@ -78,6 +65,13 @@ type ConnectedEndpoint struct {
|
||||
// prevent lots of small messages from filling the real send buffer
|
||||
// size on the host.
|
||||
sndbuf int `state:"nosave"`
|
||||
|
||||
// mu protects the fields below.
|
||||
mu sync.RWMutex `state:"nosave"`
|
||||
|
||||
// file is an *fd.FD containing the FD backing this endpoint. It must be
|
||||
// set to nil if it has been closed.
|
||||
file *fd.FD `state:"nosave"`
|
||||
}
|
||||
|
||||
// init performs initialization required for creating new ConnectedEndpoints and
|
||||
@@ -208,9 +202,6 @@ func newSocket(ctx context.Context, orgfd int, saveable bool) (*fs.File, error)
|
||||
func (c *ConnectedEndpoint) Send(data [][]byte, controlMessages transport.ControlMessages, from tcpip.FullAddress) (uintptr, bool, *syserr.Error) {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
if c.writeClosed {
|
||||
return 0, false, syserr.ErrClosedForSend
|
||||
}
|
||||
|
||||
if !controlMessages.Empty() {
|
||||
return 0, false, syserr.ErrInvalidEndpointState
|
||||
@@ -244,8 +235,13 @@ func (c *ConnectedEndpoint) SendNotify() {}
|
||||
// CloseSend implements transport.ConnectedEndpoint.CloseSend.
|
||||
func (c *ConnectedEndpoint) CloseSend() {
|
||||
c.mu.Lock()
|
||||
c.writeClosed = true
|
||||
c.mu.Unlock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if err := syscall.Shutdown(c.file.FD(), syscall.SHUT_WR); err != nil {
|
||||
// A well-formed UDS shutdown can't fail. See
|
||||
// net/unix/af_unix.c:unix_shutdown.
|
||||
panic(fmt.Sprintf("failed write shutdown on host socket %+v: %v", c, err))
|
||||
}
|
||||
}
|
||||
|
||||
// CloseNotify implements transport.ConnectedEndpoint.CloseNotify.
|
||||
@@ -255,9 +251,7 @@ func (c *ConnectedEndpoint) CloseNotify() {}
|
||||
func (c *ConnectedEndpoint) Writable() bool {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
if c.writeClosed {
|
||||
return true
|
||||
}
|
||||
|
||||
return fdnotifier.NonBlockingPoll(int32(c.file.FD()), waiter.EventOut)&waiter.EventOut != 0
|
||||
}
|
||||
|
||||
@@ -285,9 +279,6 @@ func (c *ConnectedEndpoint) EventUpdate() {
|
||||
func (c *ConnectedEndpoint) Recv(data [][]byte, creds bool, numRights uintptr, peek bool) (uintptr, uintptr, transport.ControlMessages, bool, tcpip.FullAddress, bool, *syserr.Error) {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
if c.readClosed {
|
||||
return 0, 0, transport.ControlMessages{}, false, tcpip.FullAddress{}, false, syserr.ErrClosedForReceive
|
||||
}
|
||||
|
||||
var cm unet.ControlMessage
|
||||
if numRights > 0 {
|
||||
@@ -344,31 +335,34 @@ func (c *ConnectedEndpoint) RecvNotify() {}
|
||||
// CloseRecv implements transport.Receiver.CloseRecv.
|
||||
func (c *ConnectedEndpoint) CloseRecv() {
|
||||
c.mu.Lock()
|
||||
c.readClosed = true
|
||||
c.mu.Unlock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if err := syscall.Shutdown(c.file.FD(), syscall.SHUT_RD); err != nil {
|
||||
// A well-formed UDS shutdown can't fail. See
|
||||
// net/unix/af_unix.c:unix_shutdown.
|
||||
panic(fmt.Sprintf("failed read shutdown on host socket %+v: %v", c, err))
|
||||
}
|
||||
}
|
||||
|
||||
// Readable implements transport.Receiver.Readable.
|
||||
func (c *ConnectedEndpoint) Readable() bool {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
if c.readClosed {
|
||||
return true
|
||||
}
|
||||
|
||||
return fdnotifier.NonBlockingPoll(int32(c.file.FD()), waiter.EventIn)&waiter.EventIn != 0
|
||||
}
|
||||
|
||||
// SendQueuedSize implements transport.Receiver.SendQueuedSize.
|
||||
func (c *ConnectedEndpoint) SendQueuedSize() int64 {
|
||||
// SendQueuedSize isn't supported for host sockets because we don't allow the
|
||||
// sentry to call ioctl(2).
|
||||
// TODO(gvisor.dev/issue/273): SendQueuedSize isn't supported for host
|
||||
// sockets because we don't allow the sentry to call ioctl(2).
|
||||
return -1
|
||||
}
|
||||
|
||||
// RecvQueuedSize implements transport.Receiver.RecvQueuedSize.
|
||||
func (c *ConnectedEndpoint) RecvQueuedSize() int64 {
|
||||
// RecvQueuedSize isn't supported for host sockets because we don't allow the
|
||||
// sentry to call ioctl(2).
|
||||
// TODO(gvisor.dev/issue/273): RecvQueuedSize isn't supported for host
|
||||
// sockets because we don't allow the sentry to call ioctl(2).
|
||||
return -1
|
||||
}
|
||||
|
||||
|
||||
@@ -198,20 +198,6 @@ func TestListen(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSend(t *testing.T) {
|
||||
e := ConnectedEndpoint{writeClosed: true}
|
||||
if _, _, err := e.Send(nil, transport.ControlMessages{}, tcpip.FullAddress{}); err != syserr.ErrClosedForSend {
|
||||
t.Errorf("Got %#v.Send() = %v, want = %v", e, err, syserr.ErrClosedForSend)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecv(t *testing.T) {
|
||||
e := ConnectedEndpoint{readClosed: true}
|
||||
if _, _, _, _, _, _, err := e.Recv(nil, false, 0, false); err != syserr.ErrClosedForReceive {
|
||||
t.Errorf("Got %#v.Recv() = %v, want = %v", e, err, syserr.ErrClosedForReceive)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPasscred(t *testing.T) {
|
||||
e := ConnectedEndpoint{}
|
||||
if got, want := e.Passcred(), false; got != want {
|
||||
@@ -244,20 +230,6 @@ func TestQueuedSize(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadable(t *testing.T) {
|
||||
e := ConnectedEndpoint{readClosed: true}
|
||||
if got, want := e.Readable(), true; got != want {
|
||||
t.Errorf("Got %#v.Readable() = %t, want = %t", e, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWritable(t *testing.T) {
|
||||
e := ConnectedEndpoint{writeClosed: true}
|
||||
if got, want := e.Writable(), true; got != want {
|
||||
t.Errorf("Got %#v.Writable() = %t, want = %t", e, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRelease(t *testing.T) {
|
||||
f, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
@@ -272,131 +244,3 @@ func TestRelease(t *testing.T) {
|
||||
t.Errorf("got = %#v, want = %#v", c, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClose(t *testing.T) {
|
||||
type testCase struct {
|
||||
name string
|
||||
cep *ConnectedEndpoint
|
||||
addFD bool
|
||||
f func()
|
||||
want *ConnectedEndpoint
|
||||
}
|
||||
|
||||
var tests []testCase
|
||||
|
||||
// nil is the value used by ConnectedEndpoint to indicate a closed file.
|
||||
// Non-nil files are used to check if the file gets closed.
|
||||
|
||||
f, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
t.Fatal("Creating socket:", err)
|
||||
}
|
||||
c := &ConnectedEndpoint{queue: &waiter.Queue{}, file: fd.New(f)}
|
||||
tests = append(tests, testCase{
|
||||
name: "First CloseRecv",
|
||||
cep: c,
|
||||
addFD: false,
|
||||
f: c.CloseRecv,
|
||||
want: &ConnectedEndpoint{queue: c.queue, file: c.file, readClosed: true},
|
||||
})
|
||||
|
||||
f, err = syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
t.Fatal("Creating socket:", err)
|
||||
}
|
||||
c = &ConnectedEndpoint{queue: &waiter.Queue{}, file: fd.New(f), readClosed: true}
|
||||
tests = append(tests, testCase{
|
||||
name: "Second CloseRecv",
|
||||
cep: c,
|
||||
addFD: false,
|
||||
f: c.CloseRecv,
|
||||
want: &ConnectedEndpoint{queue: c.queue, file: c.file, readClosed: true},
|
||||
})
|
||||
|
||||
f, err = syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
t.Fatal("Creating socket:", err)
|
||||
}
|
||||
c = &ConnectedEndpoint{queue: &waiter.Queue{}, file: fd.New(f)}
|
||||
tests = append(tests, testCase{
|
||||
name: "First CloseSend",
|
||||
cep: c,
|
||||
addFD: false,
|
||||
f: c.CloseSend,
|
||||
want: &ConnectedEndpoint{queue: c.queue, file: c.file, writeClosed: true},
|
||||
})
|
||||
|
||||
f, err = syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
t.Fatal("Creating socket:", err)
|
||||
}
|
||||
c = &ConnectedEndpoint{queue: &waiter.Queue{}, file: fd.New(f), writeClosed: true}
|
||||
tests = append(tests, testCase{
|
||||
name: "Second CloseSend",
|
||||
cep: c,
|
||||
addFD: false,
|
||||
f: c.CloseSend,
|
||||
want: &ConnectedEndpoint{queue: c.queue, file: c.file, writeClosed: true},
|
||||
})
|
||||
|
||||
f, err = syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
t.Fatal("Creating socket:", err)
|
||||
}
|
||||
c = &ConnectedEndpoint{queue: &waiter.Queue{}, file: fd.New(f), writeClosed: true}
|
||||
tests = append(tests, testCase{
|
||||
name: "CloseSend then CloseRecv",
|
||||
cep: c,
|
||||
addFD: true,
|
||||
f: c.CloseRecv,
|
||||
want: &ConnectedEndpoint{queue: c.queue, file: c.file, readClosed: true, writeClosed: true},
|
||||
})
|
||||
|
||||
f, err = syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
t.Fatal("Creating socket:", err)
|
||||
}
|
||||
c = &ConnectedEndpoint{queue: &waiter.Queue{}, file: fd.New(f), readClosed: true}
|
||||
tests = append(tests, testCase{
|
||||
name: "CloseRecv then CloseSend",
|
||||
cep: c,
|
||||
addFD: true,
|
||||
f: c.CloseSend,
|
||||
want: &ConnectedEndpoint{queue: c.queue, file: c.file, readClosed: true, writeClosed: true},
|
||||
})
|
||||
|
||||
f, err = syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
t.Fatal("Creating socket:", err)
|
||||
}
|
||||
c = &ConnectedEndpoint{queue: &waiter.Queue{}, file: fd.New(f), readClosed: true, writeClosed: true}
|
||||
tests = append(tests, testCase{
|
||||
name: "Full close then CloseRecv",
|
||||
cep: c,
|
||||
addFD: false,
|
||||
f: c.CloseRecv,
|
||||
want: &ConnectedEndpoint{queue: c.queue, file: c.file, readClosed: true, writeClosed: true},
|
||||
})
|
||||
|
||||
f, err = syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
t.Fatal("Creating socket:", err)
|
||||
}
|
||||
c = &ConnectedEndpoint{queue: &waiter.Queue{}, file: fd.New(f), readClosed: true, writeClosed: true}
|
||||
tests = append(tests, testCase{
|
||||
name: "Full close then CloseSend",
|
||||
cep: c,
|
||||
addFD: false,
|
||||
f: c.CloseSend,
|
||||
want: &ConnectedEndpoint{queue: c.queue, file: c.file, readClosed: true, writeClosed: true},
|
||||
})
|
||||
|
||||
for _, test := range tests {
|
||||
if test.addFD {
|
||||
fdnotifier.AddFD(int32(test.cep.file.FD()), nil)
|
||||
}
|
||||
if test.f(); !reflect.DeepEqual(test.cep, test.want) {
|
||||
t.Errorf("%s: got = %#v, want = %#v", test.name, test.cep, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,6 +246,10 @@ var allowedSyscalls = seccomp.SyscallRules{
|
||||
},
|
||||
syscall.SYS_SETITIMER: {},
|
||||
syscall.SYS_SHUTDOWN: []seccomp.Rule{
|
||||
// Used by fs/host to shutdown host sockets.
|
||||
{seccomp.AllowAny{}, seccomp.AllowValue(syscall.SHUT_RD)},
|
||||
{seccomp.AllowAny{}, seccomp.AllowValue(syscall.SHUT_WR)},
|
||||
// Used by unet to shutdown connections.
|
||||
{seccomp.AllowAny{}, seccomp.AllowValue(syscall.SHUT_RDWR)},
|
||||
},
|
||||
syscall.SYS_SIGALTSTACK: {},
|
||||
|
||||
@@ -2096,6 +2096,7 @@ cc_binary(
|
||||
deps = [
|
||||
":socket_generic_test_cases",
|
||||
":socket_test_util",
|
||||
":socket_unix_cmsg_test_cases",
|
||||
":socket_unix_test_cases",
|
||||
":unix_domain_socket_test_util",
|
||||
"//test/util:test_main",
|
||||
@@ -2369,6 +2370,7 @@ cc_binary(
|
||||
deps = [
|
||||
":socket_generic_test_cases",
|
||||
":socket_test_util",
|
||||
":socket_unix_cmsg_test_cases",
|
||||
":socket_unix_test_cases",
|
||||
":unix_domain_socket_test_util",
|
||||
"//test/util:test_main",
|
||||
@@ -2490,6 +2492,26 @@ cc_library(
|
||||
alwayslink = 1,
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "socket_unix_cmsg_test_cases",
|
||||
testonly = 1,
|
||||
srcs = [
|
||||
"socket_unix_cmsg.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"socket_unix_cmsg.h",
|
||||
],
|
||||
deps = [
|
||||
":socket_test_util",
|
||||
":unix_domain_socket_test_util",
|
||||
"//test/util:test_util",
|
||||
"//test/util:thread_util",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_googletest//:gtest",
|
||||
],
|
||||
alwayslink = 1,
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "socket_stream_blocking_test_cases",
|
||||
testonly = 1,
|
||||
@@ -2733,6 +2755,7 @@ cc_binary(
|
||||
linkstatic = 1,
|
||||
deps = [
|
||||
":socket_test_util",
|
||||
":socket_unix_cmsg_test_cases",
|
||||
":socket_unix_test_cases",
|
||||
":unix_domain_socket_test_util",
|
||||
"//test/util:test_main",
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "test/syscalls/linux/socket_generic.h"
|
||||
#include "test/syscalls/linux/socket_test_util.h"
|
||||
#include "test/syscalls/linux/socket_unix.h"
|
||||
#include "test/syscalls/linux/socket_unix_cmsg.h"
|
||||
#include "test/syscalls/linux/unix_domain_socket_test_util.h"
|
||||
#include "test/util/test_util.h"
|
||||
|
||||
@@ -38,5 +39,9 @@ INSTANTIATE_TEST_SUITE_P(
|
||||
AbstractUnixSockets, UnixSocketPairTest,
|
||||
::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
AbstractUnixSockets, UnixSocketPairCmsgTest,
|
||||
::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "test/syscalls/linux/socket_generic.h"
|
||||
#include "test/syscalls/linux/socket_test_util.h"
|
||||
#include "test/syscalls/linux/socket_unix.h"
|
||||
#include "test/syscalls/linux/socket_unix_cmsg.h"
|
||||
#include "test/syscalls/linux/unix_domain_socket_test_util.h"
|
||||
#include "test/util/test_util.h"
|
||||
|
||||
@@ -38,5 +39,9 @@ INSTANTIATE_TEST_SUITE_P(
|
||||
FilesystemUnixSockets, UnixSocketPairTest,
|
||||
::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
FilesystemUnixSockets, UnixSocketPairCmsgTest,
|
||||
::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
|
||||
+77
-1431
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,30 @@
|
||||
// Copyright 2018 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_CMSG_H_
|
||||
#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_CMSG_H_
|
||||
|
||||
#include "test/syscalls/linux/socket_test_util.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
// Test fixture for tests that apply to pairs of connected unix sockets about
|
||||
// control messages.
|
||||
using UnixSocketPairCmsgTest = SocketPairTest;
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
|
||||
#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_CMSG_H_
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include "test/syscalls/linux/socket_test_util.h"
|
||||
#include "test/syscalls/linux/socket_unix.h"
|
||||
#include "test/syscalls/linux/socket_unix_cmsg.h"
|
||||
#include "test/syscalls/linux/unix_domain_socket_test_util.h"
|
||||
#include "test/util/test_util.h"
|
||||
|
||||
@@ -33,5 +34,9 @@ INSTANTIATE_TEST_SUITE_P(
|
||||
AllUnixDomainSockets, UnixSocketPairTest,
|
||||
::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
AllUnixDomainSockets, UnixSocketPairCmsgTest,
|
||||
::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
|
||||
Reference in New Issue
Block a user