1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
|
// Copyright 2019 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.
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
// Package fdchannel implements passing file descriptors between processes over
// Unix domain sockets.
package fdchannel
import (
"fmt"
"unsafe"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/gohacks"
)
// int32 is the real type of a file descriptor.
const sizeofInt32 = int(unsafe.Sizeof(int32(0)))
// NewConnectedSockets returns a pair of file descriptors, owned by the caller,
// representing connected sockets that may be passed to separate calls to
// NewEndpoint to create connected Endpoints.
func NewConnectedSockets() ([2]int, error) {
return unix.Socketpair(unix.AF_UNIX, unix.SOCK_SEQPACKET|unix.SOCK_CLOEXEC, 0)
}
// Endpoint sends file descriptors to, and receives them from, another
// connected Endpoint.
//
// Endpoint is not copyable or movable by value.
type Endpoint struct {
sockfd int32
msghdr unix.Msghdr
cmsg *unix.Cmsghdr // followed by sizeofInt32 bytes of data
}
// Init must be called on zero-value Endpoints before first use. sockfd must be
// a blocking AF_UNIX SOCK_SEQPACKET socket.
func (ep *Endpoint) Init(sockfd int) {
// "Datagram sockets in various domains (e.g., the UNIX and Internet
// domains) permit zero-length datagrams." - recv(2). Experimentally,
// sendmsg+recvmsg for a zero-length datagram is slightly faster than
// sendmsg+recvmsg for a single byte over a stream socket.
cmsgSlice := make([]byte, unix.CmsgSpace(sizeofInt32))
cmsgSliceHdr := (*gohacks.SliceHeader)(unsafe.Pointer(&cmsgSlice))
ep.sockfd = int32(sockfd)
ep.msghdr.Control = (*byte)(cmsgSliceHdr.Data)
ep.cmsg = (*unix.Cmsghdr)(cmsgSliceHdr.Data)
// ep.msghdr.Controllen and ep.cmsg.* are mutated by recvmsg(2), so they're
// set before calling sendmsg/recvmsg.
}
// NewEndpoint is a convenience function that returns an initialized Endpoint
// allocated on the heap.
func NewEndpoint(sockfd int) *Endpoint {
ep := &Endpoint{}
ep.Init(sockfd)
return ep
}
// Destroy releases resources owned by ep. No other Endpoint methods may be
// called after Destroy.
func (ep *Endpoint) Destroy() {
unix.Close(int(ep.sockfd))
ep.sockfd = -1
}
// Shutdown causes concurrent and future calls to ep.SendFD(), ep.RecvFD(), and
// ep.RecvFDNonblock(), as well as the same calls in the connected Endpoint, to
// unblock and return errors. It does not wait for concurrent calls to return.
//
// Shutdown is the only Endpoint method that may be called concurrently with
// other methods.
func (ep *Endpoint) Shutdown() {
unix.Shutdown(int(ep.sockfd), unix.SHUT_RDWR)
}
// SendFD sends the open file description represented by the given file
// descriptor to the connected Endpoint.
func (ep *Endpoint) SendFD(fd int) error {
cmsgLen := unix.CmsgLen(sizeofInt32)
ep.cmsg.Level = unix.SOL_SOCKET
ep.cmsg.Type = unix.SCM_RIGHTS
ep.cmsg.SetLen(cmsgLen)
*ep.cmsgData() = int32(fd)
ep.msghdr.SetControllen(cmsgLen)
_, _, e := unix.Syscall(unix.SYS_SENDMSG, uintptr(ep.sockfd), uintptr(unsafe.Pointer(&ep.msghdr)), 0)
if e != 0 {
return e
}
return nil
}
// RecvFD receives an open file description from the connected Endpoint and
// returns a file descriptor representing it, owned by the caller.
func (ep *Endpoint) RecvFD() (int, error) {
return ep.recvFD(false)
}
// RecvFDNonblock receives an open file description from the connected Endpoint
// and returns a file descriptor representing it, owned by the caller. If there
// are no pending receivable open file descriptions, RecvFDNonblock returns
// (<unspecified>, EAGAIN or EWOULDBLOCK).
func (ep *Endpoint) RecvFDNonblock() (int, error) {
return ep.recvFD(true)
}
func (ep *Endpoint) recvFD(nonblock bool) (int, error) {
cmsgLen := unix.CmsgLen(sizeofInt32)
ep.msghdr.SetControllen(cmsgLen)
var e unix.Errno
if nonblock {
_, _, e = unix.RawSyscall(unix.SYS_RECVMSG, uintptr(ep.sockfd), uintptr(unsafe.Pointer(&ep.msghdr)), unix.MSG_TRUNC|unix.MSG_DONTWAIT)
} else {
_, _, e = unix.Syscall(unix.SYS_RECVMSG, uintptr(ep.sockfd), uintptr(unsafe.Pointer(&ep.msghdr)), unix.MSG_TRUNC)
}
if e != 0 {
return -1, e
}
if int(ep.msghdr.Controllen) != cmsgLen {
return -1, fmt.Errorf("received control message has incorrect length: got %d, wanted %d", ep.msghdr.Controllen, cmsgLen)
}
if ep.cmsg.Level != unix.SOL_SOCKET || ep.cmsg.Type != unix.SCM_RIGHTS {
return -1, fmt.Errorf("received control message has incorrect (level, type): got (%v, %v), wanted (%v, %v)", ep.cmsg.Level, ep.cmsg.Type, unix.SOL_SOCKET, unix.SCM_RIGHTS)
}
return int(*ep.cmsgData()), nil
}
func (ep *Endpoint) cmsgData() *int32 {
// unix.CmsgLen(0) == unix.cmsgAlignOf(unix.SizeofCmsghdr)
return (*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(ep.cmsg)) + uintptr(unix.CmsgLen(0))))
}
|