mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Add //pkg/fdchannel.
To accompany flipcall connections in cases where passing FDs is required (as for gofers). PiperOrigin-RevId: 255062277
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
load("//tools/go_stateify:defs.bzl", "go_library", "go_test")
|
||||
|
||||
package(licenses = ["notice"])
|
||||
|
||||
go_library(
|
||||
name = "fdchannel",
|
||||
srcs = ["fdchannel_unsafe.go"],
|
||||
importpath = "gvisor.dev/gvisor/pkg/fdchannel",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "fdchannel_test",
|
||||
size = "small",
|
||||
srcs = ["fdchannel_test.go"],
|
||||
embed = [":fdchannel"],
|
||||
)
|
||||
@@ -0,0 +1,131 @@
|
||||
// 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.
|
||||
|
||||
package fdchannel
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sync"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestSendRecvFD(t *testing.T) {
|
||||
sendFile, err := ioutil.TempFile("", "fdchannel_test_")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temporary file: %v", err)
|
||||
}
|
||||
defer sendFile.Close()
|
||||
|
||||
chanFDs, err := NewConnectedSockets()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create fdchannel sockets: %v", err)
|
||||
}
|
||||
sendEP := NewEndpoint(chanFDs[0])
|
||||
defer sendEP.Destroy()
|
||||
recvEP := NewEndpoint(chanFDs[1])
|
||||
defer recvEP.Destroy()
|
||||
|
||||
recvFD, err := recvEP.RecvFDNonblock()
|
||||
if err != syscall.EAGAIN && err != syscall.EWOULDBLOCK {
|
||||
t.Errorf("RecvFDNonblock before SendFD: got (%d, %v), wanted (<unspecified>, EAGAIN or EWOULDBLOCK", recvFD, err)
|
||||
}
|
||||
|
||||
if err := sendEP.SendFD(int(sendFile.Fd())); err != nil {
|
||||
t.Fatalf("SendFD failed: %v", err)
|
||||
}
|
||||
recvFD, err = recvEP.RecvFD()
|
||||
if err != nil {
|
||||
t.Fatalf("RecvFD failed: %v", err)
|
||||
}
|
||||
recvFile := os.NewFile(uintptr(recvFD), "received file")
|
||||
defer recvFile.Close()
|
||||
|
||||
sendInfo, err := sendFile.Stat()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to stat sent file: %v", err)
|
||||
}
|
||||
sendInfoSys := sendInfo.Sys()
|
||||
sendStat, ok := sendInfoSys.(*syscall.Stat_t)
|
||||
if !ok {
|
||||
t.Fatalf("sent file's FileInfo is backed by unknown type %T", sendInfoSys)
|
||||
}
|
||||
|
||||
recvInfo, err := recvFile.Stat()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to stat received file: %v", err)
|
||||
}
|
||||
recvInfoSys := recvInfo.Sys()
|
||||
recvStat, ok := recvInfoSys.(*syscall.Stat_t)
|
||||
if !ok {
|
||||
t.Fatalf("received file's FileInfo is backed by unknown type %T", recvInfoSys)
|
||||
}
|
||||
|
||||
if sendStat.Dev != recvStat.Dev || sendStat.Ino != recvStat.Ino {
|
||||
t.Errorf("sent file (dev=%d, ino=%d) does not match received file (dev=%d, ino=%d)", sendStat.Dev, sendStat.Ino, recvStat.Dev, recvStat.Ino)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShutdownThenRecvFD(t *testing.T) {
|
||||
sendFile, err := ioutil.TempFile("", "fdchannel_test_")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temporary file: %v", err)
|
||||
}
|
||||
defer sendFile.Close()
|
||||
|
||||
chanFDs, err := NewConnectedSockets()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create fdchannel sockets: %v", err)
|
||||
}
|
||||
sendEP := NewEndpoint(chanFDs[0])
|
||||
defer sendEP.Destroy()
|
||||
recvEP := NewEndpoint(chanFDs[1])
|
||||
defer recvEP.Destroy()
|
||||
|
||||
recvEP.Shutdown()
|
||||
if _, err := recvEP.RecvFD(); err == nil {
|
||||
t.Error("RecvFD succeeded unexpectedly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecvFDThenShutdown(t *testing.T) {
|
||||
sendFile, err := ioutil.TempFile("", "fdchannel_test_")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temporary file: %v", err)
|
||||
}
|
||||
defer sendFile.Close()
|
||||
|
||||
chanFDs, err := NewConnectedSockets()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create fdchannel sockets: %v", err)
|
||||
}
|
||||
sendEP := NewEndpoint(chanFDs[0])
|
||||
defer sendEP.Destroy()
|
||||
recvEP := NewEndpoint(chanFDs[1])
|
||||
defer recvEP.Destroy()
|
||||
|
||||
var receiverWG sync.WaitGroup
|
||||
receiverWG.Add(1)
|
||||
go func() {
|
||||
defer receiverWG.Done()
|
||||
if _, err := recvEP.RecvFD(); err == nil {
|
||||
t.Error("RecvFD succeeded unexpectedly")
|
||||
}
|
||||
}()
|
||||
defer receiverWG.Wait()
|
||||
time.Sleep(time.Second) // to ensure recvEP.RecvFD() has blocked
|
||||
recvEP.Shutdown()
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// 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.
|
||||
|
||||
// +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"
|
||||
"reflect"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// 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 syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_SEQPACKET|syscall.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 // accessed using atomic memory operations
|
||||
msghdr syscall.Msghdr
|
||||
cmsg *syscall.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, syscall.CmsgSpace(sizeofInt32))
|
||||
cmsgReflect := (*reflect.SliceHeader)((unsafe.Pointer)(&cmsgSlice))
|
||||
ep.sockfd = int32(sockfd)
|
||||
ep.msghdr.Control = (*byte)((unsafe.Pointer)(cmsgReflect.Data))
|
||||
ep.cmsg = (*syscall.Cmsghdr)((unsafe.Pointer)(cmsgReflect.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() {
|
||||
// These need not use sync/atomic since there must not be any concurrent
|
||||
// calls to Endpoint methods.
|
||||
if ep.sockfd >= 0 {
|
||||
syscall.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() {
|
||||
if sockfd := int(atomic.SwapInt32(&ep.sockfd, -1)); sockfd >= 0 {
|
||||
syscall.Shutdown(sockfd, syscall.SHUT_RDWR)
|
||||
syscall.Close(sockfd)
|
||||
}
|
||||
}
|
||||
|
||||
// SendFD sends the open file description represented by the given file
|
||||
// descriptor to the connected Endpoint.
|
||||
func (ep *Endpoint) SendFD(fd int) error {
|
||||
cmsgLen := syscall.CmsgLen(sizeofInt32)
|
||||
ep.cmsg.Level = syscall.SOL_SOCKET
|
||||
ep.cmsg.Type = syscall.SCM_RIGHTS
|
||||
ep.cmsg.SetLen(cmsgLen)
|
||||
*ep.cmsgData() = int32(fd)
|
||||
ep.msghdr.SetControllen(cmsgLen)
|
||||
_, _, e := syscall.Syscall(syscall.SYS_SENDMSG, uintptr(atomic.LoadInt32(&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(0)
|
||||
}
|
||||
|
||||
// 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(syscall.MSG_DONTWAIT)
|
||||
}
|
||||
|
||||
func (ep *Endpoint) recvFD(flags uintptr) (int, error) {
|
||||
cmsgLen := syscall.CmsgLen(sizeofInt32)
|
||||
ep.msghdr.SetControllen(cmsgLen)
|
||||
_, _, e := syscall.Syscall(syscall.SYS_RECVMSG, uintptr(atomic.LoadInt32(&ep.sockfd)), uintptr((unsafe.Pointer)(&ep.msghdr)), flags|syscall.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 != syscall.SOL_SOCKET || ep.cmsg.Type != syscall.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, syscall.SOL_SOCKET, syscall.SCM_RIGHTS)
|
||||
}
|
||||
return int(*ep.cmsgData()), nil
|
||||
}
|
||||
|
||||
func (ep *Endpoint) cmsgData() *int32 {
|
||||
// syscall.CmsgLen(0) == syscall.cmsgAlignOf(syscall.SizeofCmsghdr)
|
||||
return (*int32)((unsafe.Pointer)(uintptr((unsafe.Pointer)(ep.cmsg)) + uintptr(syscall.CmsgLen(0))))
|
||||
}
|
||||
Reference in New Issue
Block a user