Files
visor/pkg/sentry/socket/hostinet/socket.go
T
Shayon MukherjeeandgVisor bot f9d6366dfa hostinet: support checkpoint/restore
Host networking did not have checkpoint/restore support. A sandbox running with `--network=host` could not be checkpointed because `hostinet.Stack` was not savable, and restore did not define what should happen to open host sockets.

This change makes the hostinet stack savable and defines that restore contract. Host socket fds cannot cross a checkpoint boundary, so `Socket.fd` is tagged `state:"nosave"` and save does not touch host sockets. With checkpoint `--leave-running`, the original sandbox keeps serving traffic on its existing connections. If save fails, the sandbox is left unharmed.

Without `--leave-running`, the sandbox exits after save and the host kernel closes its sockets. That sends `FIN` or `RST` so remote peers do not hang on half-open connections.

In the restored sandbox, `afterLoad` sets each socket fd to `-1`. Host syscalls then fail with `EBADF`. `Readiness` reports hangup and error so `epoll` waiters wake immediately, and tasks that were blocked in I/O see `EBADF` when their syscall restarts. `State()` reports no TCP state for these sockets rather than logging a warning on every `/proc/net/tcp` read. Applications are expected to reconnect after restore.

The stack's host-derived state is not saved. This includes `/proc/net/dev`, `/proc/net/snmp`, TCP buffer sizes, and allowed socket types. During restore, runsc configures a fresh hostinet stack before seccomp filters are installed. `ReplaceConfig` then copies the destination host configuration into the deserialized stack and transfers ownership of the fresh proc net handles. The restored sandbox therefore uses the destination host configuration and limits.

A hostinet checkpoint contains no netstack state and a netstack checkpoint contains no hostinet state, so the checkpoint now records the network type in its metadata. Restore fails with a clear error when the network stack kind differs between checkpoint and restore, instead of panicking inside `ReplaceConfig` during kernel load. Sandbox and none networking both use netstack and remain interchangeable. Checkpoints taken before the network type was recorded skip this check.

The hostinet `save_resume` syscall tests are enabled because save does not touch host sockets. The save and restore syscall tests remain excluded because they expect sockets to keep working after restore.

Container tests cover restored socket behavior for TCP, UDP, `epoll`, and a blocked accept. They also cover remote peer close after checkpoint, continued service after checkpoint `--leave-running`, and the error when restoring a host networking checkpoint with sandbox networking. The focused hostinet unit tests and the hostinet `save_resume` syscall tests pass on `x86_64`. The checkpoint/restore user guide documents the restored socket behavior.

Fixes #6243

COPYBARA_INTEGRATE_REVIEW=https://github.com/google/gvisor/pull/13404 from shayonj:hostinet-save-restore-v2 734480ee348d774346ec024b6483b89ea0a91adb
PiperOrigin-RevId: 950854464
2026-07-20 08:29:43 -07:00

957 lines
30 KiB
Go

// 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.
package hostinet
import (
"fmt"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/atomicbitops"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/errors/linuxerr"
"gvisor.dev/gvisor/pkg/fdnotifier"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/marshal"
"gvisor.dev/gvisor/pkg/marshal/primitive"
"gvisor.dev/gvisor/pkg/safemem"
"gvisor.dev/gvisor/pkg/sentry/arch"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/sockfs"
"gvisor.dev/gvisor/pkg/sentry/hostfd"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/ktime"
"gvisor.dev/gvisor/pkg/sentry/socket"
"gvisor.dev/gvisor/pkg/sentry/socket/control"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/syserr"
"gvisor.dev/gvisor/pkg/usermem"
"gvisor.dev/gvisor/pkg/waiter"
)
const (
// sizeofSockaddr is the size in bytes of the largest sockaddr type
// supported by this package.
sizeofSockaddr = unix.SizeofSockaddrInet6 // sizeof(sockaddr_in6) > sizeof(sockaddr_in)
// maxControlLen is the maximum size of a control message buffer used in a
// recvmsg or sendmsg unix.
maxControlLen = 1024
)
// AllowedSocketType is a tuple of socket family, type, and protocol.
type AllowedSocketType struct {
Family int
Type int
// Protocol of AllowAllProtocols indicates that all protocols are
// allowed.
Protocol int
}
// AllowAllProtocols indicates that all protocols are allowed by the stack and
// in the syscall filters.
var AllowAllProtocols = -1
// AllowedSocketTypes are the socket types which are supported by hostinet.
// These are used to validate the arguments to socket(), and also to generate
// syscall filters.
var AllowedSocketTypes = []AllowedSocketType{
// Family, Type, Protocol.
{unix.AF_INET, unix.SOCK_STREAM, unix.IPPROTO_TCP},
{unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_UDP},
{unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_ICMP},
{unix.AF_INET6, unix.SOCK_STREAM, unix.IPPROTO_TCP},
{unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_UDP},
{unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_ICMPV6},
}
// AllowedRawSocketTypes are the socket types which are supported by hostinet
// with raw sockets enabled.
var AllowedRawSocketTypes = []AllowedSocketType{
{unix.AF_INET, unix.SOCK_RAW, unix.IPPROTO_RAW},
{unix.AF_INET, unix.SOCK_RAW, unix.IPPROTO_TCP},
{unix.AF_INET, unix.SOCK_RAW, unix.IPPROTO_UDP},
{unix.AF_INET, unix.SOCK_RAW, unix.IPPROTO_ICMP},
{unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_RAW},
{unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_TCP},
{unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_UDP},
{unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_ICMPV6},
// AF_PACKET do not allow Write or SendMsg.
{unix.AF_PACKET, unix.SOCK_DGRAM, AllowAllProtocols},
{unix.AF_PACKET, unix.SOCK_RAW, AllowAllProtocols},
}
// Socket implements socket.Socket (and by extension, vfs.FileDescriptionImpl)
// for host sockets.
//
// +stateify savable
type Socket struct {
vfsfd vfs.FileDescription
vfs.FileDescriptionDefaultImpl
vfs.LockFD
// We store metadata for hostinet sockets internally. Technically, we should
// access metadata (e.g. through stat, chmod) on the host for correctness,
// but this is not very useful for inet socket fds, which do not belong to a
// concrete file anyway.
vfs.DentryMetadataFileDescriptionImpl
socket.SendReceiveTimeout
family int // Read-only.
stype linux.SockType // Read-only.
protocol int // Read-only.
queue waiter.Queue
// If persistentEventMask is non-zero, persistentEntry is a no-op waiter
// that is registered in queue for all events in persistentEventMask.
// Mutations to persistentEventMask and persistentEntry are serialized by
// persistentEventMu.
//
// Bits in persistentEventMask are set by EventRegister(), and never unset.
// This ensures that queue.Events() does not change (necessitating calls to
// fdnotifier.UpdateFD() => epoll_ctl(EPOLL_CTL_MOD)) when EventRegister()
// and EventUnregister() are frequently called with entries with the same
// masks, as when e.g. applications repeatedly call poll() with the same
// event mask, or blocking accept() / read() / write() / recvmsg() /
// sendmsg() / etc., on the same socket.
persistentEventMu sync.Mutex `state:"nosave"`
persistentEventMask atomicbitops.Uint64
persistentEntry waiter.Entry
// 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 `state:"nosave"`
// recvClosed indicates that the socket has been shutdown for reading
// (SHUT_RD or SHUT_RDWR).
recvClosed atomicbitops.Bool
// listenBacklog is the backlog passed to the most recent listen(2).
listenBacklog atomicbitops.Int32
// savedListener is set by beforeSave if this socket is listening.
savedListener *listenerState
}
var _ = socket.Socket(&Socket{})
func newSocket(t *kernel.Task, family int, stype linux.SockType, protocol int, fd int, flags uint32) (*vfs.FileDescription, *syserr.Error) {
mnt := t.Kernel().SocketMount()
d := sockfs.NewDentry(t, mnt)
defer d.DecRef(t)
s := &Socket{
family: family,
stype: stype,
protocol: protocol,
fd: fd,
}
s.LockFD.Init(&vfs.FileLocks{})
if err := fdnotifier.AddFD(int32(fd), &s.queue); err != nil {
return nil, syserr.FromError(err)
}
vfsfd := &s.vfsfd
if err := vfsfd.Init(s, linux.O_RDWR|(flags&linux.O_NONBLOCK), t.Credentials(), mnt, d, &vfs.FileDescriptionOptions{
DenyPRead: true,
DenyPWrite: true,
UseDentryMetadata: true,
}); err != nil {
fdnotifier.RemoveFD(int32(s.fd))
return nil, syserr.FromError(err)
}
return vfsfd, nil
}
// Release implements vfs.FileDescriptionImpl.Release.
func (s *Socket) Release(ctx context.Context) {
kernel.KernelFromContext(ctx).DeleteSocket(&s.vfsfd)
if s.fd < 0 {
return
}
fdnotifier.RemoveFD(int32(s.fd))
_ = unix.Close(s.fd)
}
// Epollable implements FileDescriptionImpl.Epollable.
func (s *Socket) Epollable() bool {
return true
}
// Ioctl implements vfs.FileDescriptionImpl.
func (s *Socket) Ioctl(ctx context.Context, uio usermem.IO, sysno uintptr, args arch.SyscallArguments) (uintptr, error) {
if s.fd < 0 {
return 0, linuxerr.ECONNRESET
}
return ioctl(ctx, s.fd, uio, sysno, args)
}
// PRead implements vfs.FileDescriptionImpl.PRead.
func (s *Socket) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) {
return 0, linuxerr.ESPIPE
}
// Read implements vfs.FileDescriptionImpl.
func (s *Socket) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) {
// All flags other than RWF_NOWAIT should be ignored.
// TODO(gvisor.dev/issue/2601): Support RWF_NOWAIT.
if opts.Flags != 0 {
return 0, linuxerr.EOPNOTSUPP
}
if s.fd < 0 {
return 0, linuxerr.ECONNRESET
}
reader := hostfd.GetReadWriterAt(int32(s.fd), -1, opts.Flags)
defer hostfd.PutReadWriterAt(reader)
n, err := dst.CopyOutFrom(ctx, reader)
return int64(n), err
}
// PWrite implements vfs.FileDescriptionImpl.
func (s *Socket) PWrite(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) {
return 0, linuxerr.ESPIPE
}
// Write implements vfs.FileDescriptionImpl.
func (s *Socket) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) {
if s.family == linux.AF_PACKET {
// Don't allow Write for AF_PACKET.
return 0, linuxerr.EACCES
}
// All flags other than RWF_NOWAIT should be ignored.
// TODO(gvisor.dev/issue/2601): Support RWF_NOWAIT.
if opts.Flags != 0 {
return 0, linuxerr.EOPNOTSUPP
}
if s.fd < 0 {
if s.stype == linux.SOCK_STREAM || s.stype == linux.SOCK_SEQPACKET {
if sendSig := linux.SignalNoInfoFuncFromContext(ctx); sendSig != nil {
sendSig(linux.SIGPIPE)
}
return 0, linuxerr.EPIPE
}
return 0, linuxerr.ECONNRESET
}
writer := hostfd.GetReadWriterAt(int32(s.fd), -1, opts.Flags)
defer hostfd.PutReadWriterAt(writer)
n, err := src.CopyInTo(ctx, writer)
return int64(n), err
}
type socketProvider struct {
family int
}
// Socket implements socket.Provider.Socket.
func (p *socketProvider) Socket(t *kernel.Task, stypeflags linux.SockType, protocol int) (*vfs.FileDescription, *syserr.Error) {
// Check that we are using the host network stack.
netCtx := t.NetworkContext()
if netCtx == nil {
return nil, nil
}
stack, ok := netCtx.(*Stack)
if !ok {
return nil, nil
}
stype := stypeflags & linux.SOCK_TYPE_MASK
// Raw and packet sockets require CAP_NET_RAW.
if stype == linux.SOCK_RAW || p.family == linux.AF_PACKET {
if !t.HasCapabilityIn(linux.CAP_NET_RAW, t.NetworkNamespace().UserNamespace()) {
return nil, syserr.ErrNotPermitted
}
}
// Convert generic IPPROTO_IP protocol to the actual protocol depending
// on family and type.
if protocol == linux.IPPROTO_IP && (p.family == linux.AF_INET || p.family == linux.AF_INET6) {
switch stype {
case linux.SOCK_STREAM:
protocol = linux.IPPROTO_TCP
case linux.SOCK_DGRAM:
protocol = linux.IPPROTO_UDP
}
}
// Validate the socket based on family, type, and protocol.
var supported bool
for _, allowed := range stack.allowedSocketTypes {
isAllowedFamily := p.family == allowed.Family
isAllowedType := int(stype) == allowed.Type
isAllowedProtocol := protocol == allowed.Protocol || allowed.Protocol == AllowAllProtocols
if isAllowedFamily && isAllowedType && isAllowedProtocol {
supported = true
break
}
}
if !supported {
// Return nil error here to give other socket providers a
// chance to create this socket.
return nil, nil
}
// Conservatively ignore all flags specified by the application and add
// SOCK_NONBLOCK since socketOperations requires it.
st := int(stype) | unix.SOCK_NONBLOCK | unix.SOCK_CLOEXEC
fd, err := unix.Socket(p.family, st, protocol)
if err != nil {
return nil, syserr.FromError(err)
}
return newSocket(t, p.family, stype, protocol, fd, uint32(stypeflags&unix.SOCK_NONBLOCK))
}
// Pair implements socket.Provider.Pair.
func (p *socketProvider) Pair(t *kernel.Task, stype linux.SockType, protocol int) (*vfs.FileDescription, *vfs.FileDescription, *syserr.Error) {
// Not supported by AF_INET/AF_INET6.
return nil, nil, nil
}
// Readiness implements waiter.Waitable.Readiness.
func (s *Socket) Readiness(mask waiter.EventMask) waiter.EventMask {
if s.fd < 0 {
return waiter.EventHUp | waiter.EventErr | waiter.EventRdHUp
}
return fdnotifier.NonBlockingPoll(int32(s.fd), mask)
}
// EventRegister implements waiter.Waitable.EventRegister.
func (s *Socket) EventRegister(e *waiter.Entry) error {
if s.fd < 0 {
s.queue.EventRegister(e)
return nil
}
if em, pem := e.Mask(), waiter.EventMask(s.persistentEventMask.Load()); em&^pem != 0 {
s.persistentEventMu.Lock()
pem = waiter.EventMask(s.persistentEventMask.RacyLoad())
if newMask := pem | em; pem != newMask {
if pem == 0 {
s.persistentEntry.Init(waiter.NoopListener{}, newMask)
s.queue.EventRegister(&s.persistentEntry)
} else {
s.persistentEntry.SetQueuedMask(&s.queue, newMask)
}
if err := fdnotifier.UpdateFD(int32(s.fd)); err != nil {
if pem == 0 {
s.queue.EventUnregister(&s.persistentEntry)
}
s.persistentEventMu.Unlock()
return err
}
s.persistentEventMask.Store(uint64(newMask))
}
s.persistentEventMu.Unlock()
}
s.queue.EventRegister(e)
return nil
}
// EventUnregister implements waiter.Waitable.EventUnregister.
func (s *Socket) EventUnregister(e *waiter.Entry) {
s.queue.EventUnregister(e)
}
// eventRegisterTransient is equivalent to EventRegister, but does not keep
// events registered with fdnotifier after eventUnregisterTransient. This is
// used when there is no reason to expect the same events to be registered
// again in the future.
func (s *Socket) eventRegisterTransient(e *waiter.Entry) error {
s.queue.EventRegister(e)
if err := fdnotifier.UpdateFD(int32(s.fd)); err != nil {
s.queue.EventUnregister(e)
return err
}
return nil
}
// eventUnregisterTransient must be used instead of EventUnregister to
// unregister notifiers registered by eventRegisterTransient.
func (s *Socket) eventUnregisterTransient(e *waiter.Entry) {
s.queue.EventUnregister(e)
if err := fdnotifier.UpdateFD(int32(s.fd)); err != nil {
panic(err)
}
}
// Connect implements socket.Socket.Connect.
func (s *Socket) Connect(t *kernel.Task, sockaddr []byte, blocking bool) *syserr.Error {
if s.fd < 0 {
return syserr.ErrConnectionAborted
}
if len(sockaddr) > sizeofSockaddr {
sockaddr = sockaddr[:sizeofSockaddr]
}
_, _, errno := unix.Syscall(unix.SYS_CONNECT, uintptr(s.fd), uintptr(firstBytePtr(sockaddr)), uintptr(len(sockaddr)))
if errno == 0 {
return nil
}
// The host socket is always non-blocking, so we expect connect to
// return EINPROGRESS. If we are emulating a blocking socket, we will
// wait for the connect to complete below.
// But if we are not emulating a blocking socket, or if we got some
// other error, then return it now.
if errno != unix.EINPROGRESS || !blocking {
return syserr.FromError(translateIOSyscallError(errno))
}
// "EINPROGRESS: The socket is nonblocking and the connection cannot be
// completed immediately. It is possible to select(2) or poll(2) for
// completion by selecting the socket for writing. After select(2)
// indicates writability, use getsockopt(2) to read the SO_ERROR option at
// level SOL-SOCKET to determine whether connect() completed successfully
// (SO_ERROR is zero) or unsuccessfully (SO_ERROR is one of the usual error
// codes listed here, explaining the reason for the failure)." - connect(2)
writableMask := waiter.WritableEvents
e, ch := waiter.NewChannelEntry(writableMask)
s.eventRegisterTransient(&e)
defer s.eventUnregisterTransient(&e)
if s.Readiness(writableMask)&writableMask == 0 {
if err := t.Block(ch); err != nil {
return syserr.FromError(err)
}
}
val, err := unix.GetsockoptInt(s.fd, unix.SOL_SOCKET, unix.SO_ERROR)
if err != nil {
return syserr.FromError(err)
}
if val != 0 {
return syserr.FromError(unix.Errno(uintptr(val)))
}
// It seems like we are all good now, but Linux has left the socket
// state as CONNECTING (not CONNECTED). This is a strange quirk of
// non-blocking sockets. See tcp_finish_connect() which sets tcp state
// but not socket state.
//
// Sockets in the CONNECTING state can call connect() a second time,
// whereas CONNECTED sockets will reject the second connect() call.
// Because we are emulating a blocking socket, we want a subsequent
// connect() call to fail. So we must kick Linux to update the socket
// to state CONNECTED, which we can do by calling connect() a second
// time ourselves.
_, _, errno = unix.Syscall(unix.SYS_CONNECT, uintptr(s.fd), uintptr(firstBytePtr(sockaddr)), uintptr(len(sockaddr)))
if errno != 0 && errno != unix.EALREADY {
return syserr.FromError(translateIOSyscallError(errno))
}
return nil
}
// Accept implements socket.Socket.Accept.
func (s *Socket) Accept(t *kernel.Task, peerRequested bool, flags int, blocking bool) (int32, linux.SockAddr, uint32, *syserr.Error) {
if s.fd < 0 {
return 0, nil, 0, syserr.ErrConnectionAborted
}
var peerAddr linux.SockAddr
var peerAddrBuf []byte
var peerAddrlen uint32
var peerAddrPtr *byte
var peerAddrlenPtr *uint32
if peerRequested {
peerAddrBuf = make([]byte, sizeofSockaddr)
peerAddrlen = uint32(len(peerAddrBuf))
peerAddrPtr = &peerAddrBuf[0]
peerAddrlenPtr = &peerAddrlen
}
// Conservatively ignore all flags specified by the application and add
// SOCK_NONBLOCK since socketOpsCommon requires it.
fd, syscallErr := accept4(s.fd, peerAddrPtr, peerAddrlenPtr, unix.SOCK_NONBLOCK|unix.SOCK_CLOEXEC)
if blocking {
var ch chan struct{}
for linuxerr.Equals(linuxerr.ErrWouldBlock, syscallErr) {
if ch != nil {
if syscallErr = t.Block(ch); syscallErr != nil {
break
}
} else {
var e waiter.Entry
e, ch = waiter.NewChannelEntry(waiter.ReadableEvents | waiter.EventHUp | waiter.EventErr)
s.EventRegister(&e)
defer s.EventUnregister(&e)
}
fd, syscallErr = accept4(s.fd, peerAddrPtr, peerAddrlenPtr, unix.SOCK_NONBLOCK|unix.SOCK_CLOEXEC)
}
}
if peerRequested {
peerAddr = socket.UnmarshalSockAddr(s.family, peerAddrBuf[:peerAddrlen])
}
if syscallErr != nil {
return 0, peerAddr, peerAddrlen, syserr.FromError(syscallErr)
}
var (
kfd int32
kerr error
)
f, err := newSocket(t, s.family, s.stype, s.protocol, fd, uint32(flags&unix.SOCK_NONBLOCK))
if err != nil {
_ = unix.Close(fd)
return 0, nil, 0, err
}
defer f.DecRef(t)
kfd, kerr = t.NewFDFrom(0, f, kernel.FDFlags{
CloseOnExec: flags&unix.SOCK_CLOEXEC != 0,
})
t.Kernel().RecordSocket(f)
return kfd, peerAddr, peerAddrlen, syserr.FromError(kerr)
}
// Bind implements socket.Socket.Bind.
func (s *Socket) Bind(_ *kernel.Task, sockaddr []byte) *syserr.Error {
if s.fd < 0 {
return syserr.ErrConnectionAborted
}
if len(sockaddr) > sizeofSockaddr {
sockaddr = sockaddr[:sizeofSockaddr]
}
if err := bind(s.fd, sockaddr); err != nil {
return syserr.FromError(err)
}
return nil
}
// Listen implements socket.Socket.Listen.
func (s *Socket) Listen(_ *kernel.Task, backlog int) *syserr.Error {
if s.fd < 0 {
return syserr.ErrConnectionAborted
}
if err := unix.Listen(s.fd, backlog); err != nil {
return syserr.FromError(err)
}
s.listenBacklog.Store(int32(backlog))
return nil
}
// Shutdown implements socket.Socket.Shutdown.
func (s *Socket) Shutdown(_ *kernel.Task, how int) *syserr.Error {
if s.fd < 0 {
return syserr.ErrConnectionAborted
}
switch how {
case unix.SHUT_RD, unix.SHUT_RDWR:
// Mark the socket as closed for reading.
s.recvClosed.Store(true)
fallthrough
case unix.SHUT_WR:
return syserr.FromError(unix.Shutdown(s.fd, how))
default:
return syserr.ErrInvalidArgument
}
}
func (s *Socket) recvMsgFromHost(iovs []unix.Iovec, flags int, senderRequested bool, controlLen uint64) (uint64, int, []byte, []byte, error) {
// We always do a non-blocking recv*().
sysflags := flags | unix.MSG_DONTWAIT
msg := unix.Msghdr{}
if len(iovs) > 0 {
msg.Iov = &iovs[0]
msg.Iovlen = uint64(len(iovs))
}
var senderAddrBuf []byte
if senderRequested {
senderAddrBuf = make([]byte, sizeofSockaddr)
msg.Name = &senderAddrBuf[0]
msg.Namelen = uint32(sizeofSockaddr)
}
var controlBuf []byte
if controlLen > 0 {
if controlLen > maxControlLen {
controlLen = maxControlLen
}
controlBuf = make([]byte, controlLen)
msg.Control = &controlBuf[0]
msg.Controllen = controlLen
}
n, err := recvmsg(s.fd, &msg, sysflags)
if err != nil {
return 0 /* n */, 0 /* mFlags */, nil /* senderAddrBuf */, nil /* controlBuf */, err
}
return n, int(msg.Flags), senderAddrBuf[:msg.Namelen], controlBuf[:msg.Controllen], err
}
const allowedRecvMsgFlags = unix.MSG_CTRUNC |
unix.MSG_DONTWAIT |
unix.MSG_ERRQUEUE |
unix.MSG_OOB |
unix.MSG_PEEK |
unix.MSG_TRUNC |
unix.MSG_WAITALL
// RecvMsg implements socket.Socket.RecvMsg.
func (s *Socket) RecvMsg(t *kernel.Task, dst usermem.IOSequence, flags int, haveDeadline bool, deadline ktime.Time, senderRequested bool, controlLen uint64) (int, int, linux.SockAddr, uint32, socket.ControlMessages, *syserr.Error) {
// Only allow known and safe flags.
if flags&^allowedRecvMsgFlags != 0 {
return 0, 0, nil, 0, socket.ControlMessages{}, syserr.ErrInvalidArgument
}
if s.fd < 0 {
return 0, 0, nil, 0, socket.ControlMessages{}, syserr.ErrConnectionReset
}
var senderAddrBuf []byte
var controlBuf []byte
var msgFlags int
copyToDst := func() (int64, error) {
var n uint64
var err error
if dst.NumBytes() == 0 {
// We want to make the recvmsg(2) call to the host even if dst is empty
// to fetch control messages, sender address or errors if any occur.
n, msgFlags, senderAddrBuf, controlBuf, err = s.recvMsgFromHost(nil, flags, senderRequested, controlLen)
return int64(n), err
}
recvmsgToBlocks := safemem.ReaderFunc(func(dsts safemem.BlockSeq) (uint64, error) {
// Refuse to do anything if any part of dst.Addrs was unusable.
if uint64(dst.NumBytes()) != dsts.NumBytes() {
return 0, nil
}
if dsts.IsEmpty() {
return 0, nil
}
n, msgFlags, senderAddrBuf, controlBuf, err = s.recvMsgFromHost(safemem.IovecsFromBlockSeq(dsts), flags, senderRequested, controlLen)
return n, err
})
return dst.CopyOutFrom(t, recvmsgToBlocks)
}
var ch chan struct{}
n, err := copyToDst()
// recv*(MSG_ERRQUEUE) never blocks, even without MSG_DONTWAIT.
if flags&(unix.MSG_DONTWAIT|unix.MSG_ERRQUEUE) == 0 {
for linuxerr.Equals(linuxerr.ErrWouldBlock, err) {
// We only expect blocking to come from the actual syscall, in which
// case it can't have returned any data.
if n != 0 {
panic(fmt.Sprintf("CopyOutFrom: got (%d, %v), wanted (0, %v)", n, err, err))
}
// Are we closed for reading? No sense in trying to read if so.
if s.recvClosed.Load() {
break
}
if ch != nil {
if err = t.BlockWithDeadline(ch, haveDeadline, deadline); err != nil {
if linuxerr.Equals(linuxerr.ETIMEDOUT, err) {
err = linuxerr.ErrWouldBlock
}
break
}
} else {
var e waiter.Entry
e, ch = waiter.NewChannelEntry(waiter.ReadableEvents | waiter.EventRdHUp | waiter.EventHUp | waiter.EventErr)
s.EventRegister(&e)
defer s.EventUnregister(&e)
}
n, err = copyToDst()
}
}
if err != nil {
return 0, 0, nil, 0, socket.ControlMessages{}, syserr.FromError(err)
}
// In some circumstances (like MSG_PEEK specified), the sender address
// field is purposefully ignored. recvMsgFromHost will return an empty
// senderAddrBuf in those cases.
var senderAddr linux.SockAddr
if senderRequested && len(senderAddrBuf) > 0 {
senderAddr = socket.UnmarshalSockAddr(s.family, senderAddrBuf)
}
unixControlMessages, err := unix.ParseSocketControlMessage(controlBuf)
if err != nil {
return 0, 0, nil, 0, socket.ControlMessages{}, syserr.FromError(err)
}
return int(n), msgFlags, senderAddr, uint32(len(senderAddrBuf)), parseUnixControlMessages(unixControlMessages), nil
}
func parseUnixControlMessages(unixControlMessages []unix.SocketControlMessage) socket.ControlMessages {
controlMessages := socket.ControlMessages{}
for _, unixCmsg := range unixControlMessages {
switch unixCmsg.Header.Level {
case linux.SOL_SOCKET:
switch unixCmsg.Header.Type {
case linux.SO_TIMESTAMP:
controlMessages.IP.HasTimestamp = true
ts := linux.Timeval{}
ts.UnmarshalUnsafe(unixCmsg.Data)
controlMessages.IP.Timestamp = ts.ToTime()
}
case linux.SOL_IP:
switch unixCmsg.Header.Type {
case linux.IP_TOS:
controlMessages.IP.HasTOS = true
var tos primitive.Uint8
tos.UnmarshalUnsafe(unixCmsg.Data)
controlMessages.IP.TOS = uint8(tos)
case linux.IP_TTL:
controlMessages.IP.HasTTL = true
var ttl primitive.Uint32
ttl.UnmarshalUnsafe(unixCmsg.Data)
controlMessages.IP.TTL = uint32(ttl)
case linux.IP_PKTINFO:
controlMessages.IP.HasIPPacketInfo = true
var packetInfo linux.ControlMessageIPPacketInfo
packetInfo.UnmarshalUnsafe(unixCmsg.Data)
controlMessages.IP.PacketInfo = packetInfo
case linux.IP_RECVORIGDSTADDR:
var addr linux.SockAddrInet
addr.UnmarshalUnsafe(unixCmsg.Data)
controlMessages.IP.OriginalDstAddress = &addr
case unix.IP_RECVERR:
var errCmsg linux.SockErrCMsgIPv4
errCmsg.UnmarshalBytes(unixCmsg.Data)
controlMessages.IP.SockErr = &errCmsg
}
case linux.SOL_IPV6:
switch unixCmsg.Header.Type {
case linux.IPV6_TCLASS:
controlMessages.IP.HasTClass = true
var tclass primitive.Uint32
tclass.UnmarshalUnsafe(unixCmsg.Data)
controlMessages.IP.TClass = uint32(tclass)
case linux.IPV6_PKTINFO:
controlMessages.IP.HasIPv6PacketInfo = true
var packetInfo linux.ControlMessageIPv6PacketInfo
packetInfo.UnmarshalUnsafe(unixCmsg.Data)
controlMessages.IP.IPv6PacketInfo = packetInfo
case linux.IPV6_HOPLIMIT:
controlMessages.IP.HasHopLimit = true
var hoplimit primitive.Uint32
hoplimit.UnmarshalUnsafe(unixCmsg.Data)
controlMessages.IP.HopLimit = uint32(hoplimit)
case linux.IPV6_RECVORIGDSTADDR:
var addr linux.SockAddrInet6
addr.UnmarshalUnsafe(unixCmsg.Data)
controlMessages.IP.OriginalDstAddress = &addr
case unix.IPV6_RECVERR:
var errCmsg linux.SockErrCMsgIPv6
errCmsg.UnmarshalBytes(unixCmsg.Data)
controlMessages.IP.SockErr = &errCmsg
}
case linux.SOL_TCP:
switch unixCmsg.Header.Type {
case linux.TCP_INQ:
controlMessages.IP.HasInq = true
var inq primitive.Int32
inq.UnmarshalUnsafe(unixCmsg.Data)
controlMessages.IP.Inq = int32(inq)
}
}
}
return controlMessages
}
const allowedSendMsgFlags = unix.MSG_DONTWAIT |
unix.MSG_EOR |
unix.MSG_FASTOPEN |
unix.MSG_MORE |
unix.MSG_NOSIGNAL |
unix.MSG_OOB
// SendMsg implements socket.Socket.SendMsg.
func (s *Socket) SendMsg(t *kernel.Task, src usermem.IOSequence, to []byte, flags int, haveDeadline bool, deadline ktime.Time, controlMessages socket.ControlMessages) (int, *syserr.Error) {
if s.family == linux.AF_PACKET {
// Don't allow SendMesg for AF_PACKET.
return 0, syserr.ErrPermissionDenied
}
// Only allow known and safe flags.
if flags&^allowedSendMsgFlags != 0 {
return 0, syserr.ErrInvalidArgument
}
if s.fd < 0 {
if s.stype == linux.SOCK_STREAM || s.stype == linux.SOCK_SEQPACKET {
t.SendSignal(kernel.SignalInfoNoInfo(linux.SIGPIPE, t, t))
return 0, syserr.ErrBrokenPipe
}
return 0, syserr.ErrConnectionReset
}
// 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
}
controlBuf := make([]byte, 0, space)
// PackControlMessages will append up to space bytes to controlBuf.
controlBuf = control.PackControlMessages(t, controlMessages, controlBuf)
sendmsgFromBlocks := safemem.WriterFunc(func(srcs safemem.BlockSeq) (uint64, error) {
// Refuse to do anything if any part of src.Addrs was unusable.
if uint64(src.NumBytes()) != srcs.NumBytes() {
return 0, nil
}
if srcs.IsEmpty() && len(controlBuf) == 0 {
return 0, nil
}
// We always do a non-blocking send*().
sysflags := flags | unix.MSG_DONTWAIT
if srcs.NumBlocks() == 1 && len(controlBuf) == 0 {
// Skip allocating []unix.Iovec.
src := srcs.Head()
n, _, errno := unix.Syscall6(unix.SYS_SENDTO, uintptr(s.fd), src.Addr(), uintptr(src.Len()), uintptr(sysflags), uintptr(firstBytePtr(to)), uintptr(len(to)))
if errno != 0 {
return 0, translateIOSyscallError(errno)
}
return uint64(n), nil
}
iovs := safemem.IovecsFromBlockSeq(srcs)
msg := unix.Msghdr{
Iov: &iovs[0],
Iovlen: uint64(len(iovs)),
}
if len(to) != 0 {
msg.Name = &to[0]
msg.Namelen = uint32(len(to))
}
if len(controlBuf) != 0 {
msg.Control = &controlBuf[0]
msg.Controllen = uint64(len(controlBuf))
}
return sendmsg(s.fd, &msg, sysflags)
})
var ch chan struct{}
n, err := src.CopyInTo(t, sendmsgFromBlocks)
if flags&unix.MSG_DONTWAIT == 0 {
for linuxerr.Equals(linuxerr.ErrWouldBlock, err) {
// We only expect blocking to come from the actual syscall, in which
// case it can't have returned any data.
if n != 0 {
panic(fmt.Sprintf("CopyInTo: got (%d, %v), wanted (0, %v)", n, err, err))
}
if ch != nil {
if err = t.BlockWithDeadline(ch, haveDeadline, deadline); err != nil {
if linuxerr.Equals(linuxerr.ETIMEDOUT, err) {
err = linuxerr.ErrWouldBlock
}
break
}
} else {
var e waiter.Entry
e, ch = waiter.NewChannelEntry(waiter.WritableEvents | waiter.EventHUp | waiter.EventErr)
s.EventRegister(&e)
defer s.EventUnregister(&e)
}
n, err = src.CopyInTo(t, sendmsgFromBlocks)
}
}
return int(n), syserr.FromError(err)
}
func translateIOSyscallError(err error) error {
if err == unix.EAGAIN || err == unix.EWOULDBLOCK {
return linuxerr.ErrWouldBlock
}
return err
}
// State implements socket.Socket.State.
func (s *Socket) State() uint32 {
if s.fd < 0 {
if s.stype == linux.SOCK_STREAM {
return linux.TCP_CLOSE
}
return 0
}
info := linux.TCPInfo{}
buf := make([]byte, linux.SizeOfTCPInfo)
var err error
buf, err = getsockopt(s.fd, unix.SOL_TCP, unix.TCP_INFO, buf)
if err != nil {
if err != unix.ENOPROTOOPT {
log.Warningf("Failed to get TCP socket info from %+v: %v", s, err)
}
// For non-TCP sockets, silently ignore the failure.
return 0
}
if len(buf) != linux.SizeOfTCPInfo {
// Unmarshal below will panic if getsockopt returns a buffer of
// unexpected size.
log.Warningf("Failed to get TCP socket info from %+v: getsockopt(2) returned %d bytes, expecting %d bytes.", s, len(buf), linux.SizeOfTCPInfo)
return 0
}
info.UnmarshalUnsafe(buf[:info.SizeBytes()])
return uint32(info.State)
}
// Type implements socket.Socket.Type.
func (s *Socket) Type() (family int, skType linux.SockType, protocol int) {
return s.family, s.stype, s.protocol
}
func init() {
// Register all families in AllowedSocketTypes and AllowedRawSocket
// types. If we don't allow raw sockets, they will be rejected in the
// Socket call.
registered := make(map[int]struct{})
for _, sockType := range append(AllowedSocketTypes, AllowedRawSocketTypes...) {
fam := sockType.Family
if _, ok := registered[fam]; ok {
continue
}
socket.RegisterProvider(fam, &socketProvider{fam})
registered[fam] = struct{}{}
}
}
// GetPeerCreds implements socket.Socket.GetPeerCreds
func (s *Socket) GetPeerCreds(t *kernel.Task) (marshal.Marshallable, *syserr.Error) {
return nil, syserr.ErrNotSupported
}