2019-06-21 21:45:57 +00:00
|
|
|
// 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 flipcall
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"math/bits"
|
|
|
|
"os"
|
|
|
|
"syscall"
|
|
|
|
|
|
|
|
"gvisor.dev/gvisor/pkg/abi/linux"
|
|
|
|
"gvisor.dev/gvisor/pkg/memutil"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
pageSize = os.Getpagesize()
|
|
|
|
pageMask = pageSize - 1
|
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
if bits.OnesCount(uint(pageSize)) != 1 {
|
|
|
|
// This is depended on by roundUpToPage().
|
|
|
|
panic(fmt.Sprintf("system page size (%d) is not a power of 2", pageSize))
|
|
|
|
}
|
2019-07-31 19:54:49 +00:00
|
|
|
if uintptr(pageSize) < PacketHeaderBytes {
|
2019-06-21 21:45:57 +00:00
|
|
|
// This is required since Endpoint.Init() imposes a minimum packet
|
|
|
|
// window size of 1 page.
|
2019-07-31 19:54:49 +00:00
|
|
|
panic(fmt.Sprintf("system page size (%d) is less than packet header size (%d)", pageSize, PacketHeaderBytes))
|
2019-06-21 21:45:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// PacketWindowDescriptor represents a packet window, a range of pages in a
|
|
|
|
// shared memory file that is used to exchange packets between partner
|
|
|
|
// Endpoints.
|
|
|
|
type PacketWindowDescriptor struct {
|
|
|
|
// FD is the file descriptor representing the shared memory file.
|
|
|
|
FD int
|
|
|
|
|
|
|
|
// Offset is the offset into the shared memory file at which the packet
|
|
|
|
// window begins.
|
|
|
|
Offset int64
|
|
|
|
|
|
|
|
// Length is the size of the packet window in bytes.
|
|
|
|
Length int
|
|
|
|
}
|
|
|
|
|
|
|
|
// PacketWindowLengthForDataCap returns the minimum packet window size required
|
|
|
|
// to accommodate datagrams of the given size in bytes.
|
|
|
|
func PacketWindowLengthForDataCap(dataCap uint32) int {
|
2019-07-31 19:54:49 +00:00
|
|
|
return roundUpToPage(int(dataCap) + int(PacketHeaderBytes))
|
2019-06-21 21:45:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func roundUpToPage(x int) int {
|
|
|
|
return (x + pageMask) &^ pageMask
|
|
|
|
}
|
|
|
|
|
|
|
|
// A PacketWindowAllocator owns a shared memory file, and allocates packet
|
|
|
|
// windows from it.
|
|
|
|
type PacketWindowAllocator struct {
|
|
|
|
fd int
|
|
|
|
nextAlloc int64
|
|
|
|
fileSize int64
|
|
|
|
}
|
|
|
|
|
|
|
|
// Init must be called on zero-value PacketWindowAllocators before first use.
|
|
|
|
// If it succeeds, Destroy() must be called once the PacketWindowAllocator is
|
|
|
|
// no longer in use.
|
|
|
|
func (pwa *PacketWindowAllocator) Init() error {
|
|
|
|
fd, err := memutil.CreateMemFD("flipcall_packet_windows", linux.MFD_CLOEXEC|linux.MFD_ALLOW_SEALING)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to create memfd: %v", err)
|
|
|
|
}
|
|
|
|
// Apply F_SEAL_SHRINK to prevent either party from causing SIGBUS in the
|
|
|
|
// other by truncating the file, and F_SEAL_SEAL to prevent either party
|
|
|
|
// from applying F_SEAL_GROW or F_SEAL_WRITE.
|
|
|
|
if _, _, e := syscall.RawSyscall(syscall.SYS_FCNTL, uintptr(fd), linux.F_ADD_SEALS, linux.F_SEAL_SHRINK|linux.F_SEAL_SEAL); e != 0 {
|
|
|
|
syscall.Close(fd)
|
|
|
|
return fmt.Errorf("failed to apply memfd seals: %v", e)
|
|
|
|
}
|
|
|
|
pwa.fd = fd
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewPacketWindowAllocator is a convenience function that returns an
|
|
|
|
// initialized PacketWindowAllocator allocated on the heap.
|
|
|
|
func NewPacketWindowAllocator() (*PacketWindowAllocator, error) {
|
|
|
|
var pwa PacketWindowAllocator
|
|
|
|
if err := pwa.Init(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &pwa, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Destroy releases resources owned by pwa. This invalidates file descriptors
|
|
|
|
// previously returned by pwa.FD() and pwd.Allocate().
|
|
|
|
func (pwa *PacketWindowAllocator) Destroy() {
|
|
|
|
syscall.Close(pwa.fd)
|
|
|
|
}
|
|
|
|
|
|
|
|
// FD represents the file descriptor of the shared memory file backing pwa.
|
|
|
|
func (pwa *PacketWindowAllocator) FD() int {
|
|
|
|
return pwa.fd
|
|
|
|
}
|
|
|
|
|
|
|
|
// Allocate allocates a new packet window of at least the given size and
|
|
|
|
// returns a PacketWindowDescriptor representing it.
|
|
|
|
//
|
|
|
|
// Preconditions: size > 0.
|
|
|
|
func (pwa *PacketWindowAllocator) Allocate(size int) (PacketWindowDescriptor, error) {
|
|
|
|
if size <= 0 {
|
|
|
|
return PacketWindowDescriptor{}, fmt.Errorf("invalid size: %d", size)
|
|
|
|
}
|
|
|
|
// Page-align size to ensure that pwa.nextAlloc remains page-aligned.
|
|
|
|
size = roundUpToPage(size)
|
|
|
|
if size <= 0 {
|
|
|
|
return PacketWindowDescriptor{}, fmt.Errorf("size %d overflows after rounding up to page size", size)
|
|
|
|
}
|
|
|
|
end := pwa.nextAlloc + int64(size) // overflow checked by ensureFileSize
|
|
|
|
if err := pwa.ensureFileSize(end); err != nil {
|
|
|
|
return PacketWindowDescriptor{}, err
|
|
|
|
}
|
|
|
|
start := pwa.nextAlloc
|
|
|
|
pwa.nextAlloc = end
|
|
|
|
return PacketWindowDescriptor{
|
|
|
|
FD: pwa.fd,
|
|
|
|
Offset: start,
|
|
|
|
Length: size,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pwa *PacketWindowAllocator) ensureFileSize(min int64) error {
|
|
|
|
if min <= 0 {
|
|
|
|
return fmt.Errorf("file size would overflow")
|
|
|
|
}
|
|
|
|
if pwa.fileSize >= min {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
newSize := 2 * pwa.fileSize
|
|
|
|
if newSize == 0 {
|
|
|
|
newSize = int64(pageSize)
|
|
|
|
}
|
|
|
|
for newSize < min {
|
|
|
|
newNewSize := newSize * 2
|
|
|
|
if newNewSize <= 0 {
|
|
|
|
return fmt.Errorf("file size would overflow")
|
|
|
|
}
|
|
|
|
newSize = newNewSize
|
|
|
|
}
|
|
|
|
if err := syscall.Ftruncate(pwa.fd, newSize); err != nil {
|
|
|
|
return fmt.Errorf("ftruncate failed: %v", err)
|
|
|
|
}
|
|
|
|
pwa.fileSize = newSize
|
|
|
|
return nil
|
|
|
|
}
|