gvisor/pkg/sentry/socket/unix/transport/connectionless.go

219 lines
5.7 KiB
Go
Raw Normal View History

// 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 transport
import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/sentry/context"
"gvisor.dev/gvisor/pkg/syserr"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/waiter"
)
// connectionlessEndpoint is a unix endpoint for unix sockets that support operating in
// a conectionless fashon.
//
// Specifically, this means datagram unix sockets not created with
// socketpair(2).
//
// +stateify savable
type connectionlessEndpoint struct {
baseEndpoint
}
var (
_ = BoundEndpoint((*connectionlessEndpoint)(nil))
_ = Endpoint((*connectionlessEndpoint)(nil))
)
// NewConnectionless creates a new unbound dgram endpoint.
func NewConnectionless(ctx context.Context) Endpoint {
ep := &connectionlessEndpoint{baseEndpoint{Queue: &waiter.Queue{}}}
ep.receiver = &queueReceiver{readQueue: &queue{ReaderQueue: ep.Queue, WriterQueue: &waiter.Queue{}, limit: initialLimit}}
return ep
}
// isBound returns true iff the endpoint is bound.
func (e *connectionlessEndpoint) isBound() bool {
return e.path != ""
}
// Close puts the endpoint in a closed state and frees all resources associated
// with it.
//
// The socket will be a fresh state after a call to close and may be reused.
// That is, close may be used to "unbind" or "disconnect" the socket in error
// paths.
func (e *connectionlessEndpoint) Close() {
e.Lock()
var r Receiver
if e.Connected() {
e.receiver.CloseRecv()
r = e.receiver
e.receiver = nil
e.connected.Release()
e.connected = nil
}
if e.isBound() {
e.path = ""
}
e.Unlock()
if r != nil {
r.CloseNotify()
r.Release()
}
}
// BidirectionalConnect implements BoundEndpoint.BidirectionalConnect.
func (e *connectionlessEndpoint) BidirectionalConnect(ctx context.Context, ce ConnectingEndpoint, returnConnect func(Receiver, ConnectedEndpoint)) *syserr.Error {
return syserr.ErrConnectionRefused
}
// UnidirectionalConnect implements BoundEndpoint.UnidirectionalConnect.
func (e *connectionlessEndpoint) UnidirectionalConnect(ctx context.Context) (ConnectedEndpoint, *syserr.Error) {
e.Lock()
r := e.receiver
e.Unlock()
if r == nil {
return nil, syserr.ErrConnectionRefused
}
q := r.(*queueReceiver).readQueue
if !q.TryIncRef() {
return nil, syserr.ErrConnectionRefused
}
return &connectedEndpoint{
endpoint: e,
writeQueue: q,
}, nil
}
// SendMsg writes data and a control message to the specified endpoint.
// This method does not block if the data cannot be written.
func (e *connectionlessEndpoint) SendMsg(ctx context.Context, data [][]byte, c ControlMessages, to BoundEndpoint) (uintptr, *syserr.Error) {
if to == nil {
return e.baseEndpoint.SendMsg(ctx, data, c, nil)
}
connected, err := to.UnidirectionalConnect(ctx)
if err != nil {
return 0, syserr.ErrInvalidEndpointState
}
defer connected.Release()
e.Lock()
n, notify, err := connected.Send(data, c, tcpip.FullAddress{Addr: tcpip.Address(e.path)})
e.Unlock()
if notify {
connected.SendNotify()
}
return n, err
}
// Type implements Endpoint.Type.
func (e *connectionlessEndpoint) Type() linux.SockType {
return linux.SOCK_DGRAM
}
// Connect attempts to connect directly to server.
func (e *connectionlessEndpoint) Connect(ctx context.Context, server BoundEndpoint) *syserr.Error {
connected, err := server.UnidirectionalConnect(ctx)
if err != nil {
return err
}
e.Lock()
e.connected = connected
e.Unlock()
return nil
}
// Listen starts listening on the connection.
func (e *connectionlessEndpoint) Listen(int) *syserr.Error {
return syserr.ErrNotSupported
}
// Accept accepts a new connection.
func (e *connectionlessEndpoint) Accept() (Endpoint, *syserr.Error) {
return nil, syserr.ErrNotSupported
}
// Bind binds the connection.
//
// For Unix endpoints, this _only sets the address associated with the socket_.
// Work associated with sockets in the filesystem or finding those sockets must
// be done by a higher level.
//
// Bind will fail only if the socket is connected, bound or the passed address
// is invalid (the empty string).
func (e *connectionlessEndpoint) Bind(addr tcpip.FullAddress, commit func() *syserr.Error) *syserr.Error {
e.Lock()
defer e.Unlock()
if e.isBound() {
return syserr.ErrAlreadyBound
}
if addr.Addr == "" {
// The empty string is not permitted.
return syserr.ErrBadLocalAddress
}
if commit != nil {
if err := commit(); err != nil {
return err
}
}
// Save the bound address.
e.path = string(addr.Addr)
return nil
}
// Readiness returns the current readiness of the endpoint. For example, if
// waiter.EventIn is set, the endpoint is immediately readable.
func (e *connectionlessEndpoint) Readiness(mask waiter.EventMask) waiter.EventMask {
e.Lock()
defer e.Unlock()
ready := waiter.EventMask(0)
if mask&waiter.EventIn != 0 && e.receiver.Readable() {
ready |= waiter.EventIn
}
if e.Connected() {
if mask&waiter.EventOut != 0 && e.connected.Writable() {
ready |= waiter.EventOut
}
}
return ready
}
// State implements socket.Socket.State.
func (e *connectionlessEndpoint) State() uint32 {
e.Lock()
defer e.Unlock()
switch {
case e.isBound():
return linux.SS_UNCONNECTED
case e.Connected():
return linux.SS_CONNECTING
default:
return linux.SS_DISCONNECTING
}
}