mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
223 lines
6.3 KiB
Go
223 lines
6.3 KiB
Go
// Copyright 2021 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 linux
|
|
// +build linux
|
|
|
|
package sharedmem
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
"golang.org/x/sys/unix"
|
|
"gvisor.dev/gvisor/pkg/eventfd"
|
|
)
|
|
|
|
const (
|
|
// DefaultQueueDataSize is the size of the shared memory data region that
|
|
// holds the scatter/gather buffers.
|
|
DefaultQueueDataSize = 1 << 20 // 1MiB
|
|
|
|
// DefaultQueuePipeSize is the size of the pipe that holds the packet descriptors.
|
|
//
|
|
// Assuming each packet data is approximately 1280 bytes (IPv6 Minimum MTU)
|
|
// then we can hold approximately 1024*1024/1280 ~ 819 packets in the data
|
|
// area. Which means the pipe needs to be big enough to hold 819
|
|
// descriptors.
|
|
//
|
|
// Each descriptor is approximately 8 (slot descriptor in pipe) +
|
|
// 16 (packet descriptor) + 12 (for buffer descriptor) assuming each packet is
|
|
// stored in exactly 1 buffer descriptor (see queue/tx.go and pipe/tx.go.)
|
|
//
|
|
// Which means we need approximately 36*819 ~ 29 KiB to store all packet
|
|
// descriptors. We could go with a 32 KiB pipe but to give it some slack in
|
|
// how the upper layer may make use of the scatter gather buffers we double
|
|
// this to hold enough descriptors.
|
|
DefaultQueuePipeSize = 64 << 10 // 64KiB
|
|
|
|
// DefaultSharedDataSize is the size of the sharedData region used to
|
|
// enable/disable notifications.
|
|
DefaultSharedDataSize = 4 << 10 // 4KiB
|
|
|
|
// DefaultBufferSize is the size of each individual buffer that the data
|
|
// region is broken down into to hold packet data. Should be larger than
|
|
// 1500 + 14 (Ethernet header) + 10 (VirtIO header) to fit each packet
|
|
// in a single buffer.
|
|
DefaultBufferSize = 2048
|
|
|
|
// DefaultTmpDir is the path used to create the memory files if a path
|
|
// is not provided.
|
|
DefaultTmpDir = "/dev/shm"
|
|
)
|
|
|
|
// A QueuePair represents a pair of TX/RX queues.
|
|
type QueuePair struct {
|
|
// txCfg is the QueueConfig to be used for transmit queue.
|
|
txCfg QueueConfig
|
|
|
|
// rxCfg is the QueueConfig to be used for receive queue.
|
|
rxCfg QueueConfig
|
|
}
|
|
|
|
// QueueOptions allows queue specific configuration to be specified when
|
|
// creating a QueuePair.
|
|
type QueueOptions struct {
|
|
// SharedMemPath is the path to use to create the shared memory backing
|
|
// files for the queue.
|
|
//
|
|
// If unspecified it defaults to "/dev/shm".
|
|
SharedMemPath string
|
|
}
|
|
|
|
// NewQueuePair creates a shared memory QueuePair.
|
|
func NewQueuePair(opts QueueOptions) (*QueuePair, error) {
|
|
txCfg, err := createQueueFDs(opts.SharedMemPath, queueSizes{
|
|
dataSize: DefaultQueueDataSize,
|
|
txPipeSize: DefaultQueuePipeSize,
|
|
rxPipeSize: DefaultQueuePipeSize,
|
|
sharedDataSize: DefaultSharedDataSize,
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create tx queue: %s", err)
|
|
}
|
|
|
|
rxCfg, err := createQueueFDs(opts.SharedMemPath, queueSizes{
|
|
dataSize: DefaultQueueDataSize,
|
|
txPipeSize: DefaultQueuePipeSize,
|
|
rxPipeSize: DefaultQueuePipeSize,
|
|
sharedDataSize: DefaultSharedDataSize,
|
|
})
|
|
|
|
if err != nil {
|
|
closeFDs(txCfg)
|
|
return nil, fmt.Errorf("failed to create rx queue: %s", err)
|
|
}
|
|
|
|
return &QueuePair{
|
|
txCfg: txCfg,
|
|
rxCfg: rxCfg,
|
|
}, nil
|
|
}
|
|
|
|
// Close closes underlying tx/rx queue fds.
|
|
func (q *QueuePair) Close() {
|
|
closeFDs(q.txCfg)
|
|
closeFDs(q.rxCfg)
|
|
}
|
|
|
|
// TXQueueConfig returns the QueueConfig for the receive queue.
|
|
func (q *QueuePair) TXQueueConfig() QueueConfig {
|
|
return q.txCfg
|
|
}
|
|
|
|
// RXQueueConfig returns the QueueConfig for the transmit queue.
|
|
func (q *QueuePair) RXQueueConfig() QueueConfig {
|
|
return q.rxCfg
|
|
}
|
|
|
|
type queueSizes struct {
|
|
dataSize int64
|
|
txPipeSize int64
|
|
rxPipeSize int64
|
|
sharedDataSize int64
|
|
}
|
|
|
|
func createQueueFDs(sharedMemPath string, s queueSizes) (QueueConfig, error) {
|
|
success := false
|
|
var eventFD eventfd.Eventfd
|
|
var dataFD, txPipeFD, rxPipeFD, sharedDataFD int
|
|
defer func() {
|
|
if success {
|
|
return
|
|
}
|
|
closeFDs(QueueConfig{
|
|
EventFD: eventFD,
|
|
DataFD: dataFD,
|
|
TxPipeFD: txPipeFD,
|
|
RxPipeFD: rxPipeFD,
|
|
SharedDataFD: sharedDataFD,
|
|
})
|
|
}()
|
|
eventFD, err := eventfd.Create()
|
|
if err != nil {
|
|
return QueueConfig{}, fmt.Errorf("eventfd failed: %v", err)
|
|
}
|
|
dataFD, err = createFile(sharedMemPath, s.dataSize, false)
|
|
if err != nil {
|
|
return QueueConfig{}, fmt.Errorf("failed to create dataFD: %s", err)
|
|
}
|
|
txPipeFD, err = createFile(sharedMemPath, s.txPipeSize, true)
|
|
if err != nil {
|
|
return QueueConfig{}, fmt.Errorf("failed to create txPipeFD: %s", err)
|
|
}
|
|
rxPipeFD, err = createFile(sharedMemPath, s.rxPipeSize, true)
|
|
if err != nil {
|
|
return QueueConfig{}, fmt.Errorf("failed to create rxPipeFD: %s", err)
|
|
}
|
|
sharedDataFD, err = createFile(sharedMemPath, s.sharedDataSize, false)
|
|
if err != nil {
|
|
return QueueConfig{}, fmt.Errorf("failed to create sharedDataFD: %s", err)
|
|
}
|
|
success = true
|
|
return QueueConfig{
|
|
EventFD: eventFD,
|
|
DataFD: dataFD,
|
|
TxPipeFD: txPipeFD,
|
|
RxPipeFD: rxPipeFD,
|
|
SharedDataFD: sharedDataFD,
|
|
}, nil
|
|
}
|
|
|
|
func createFile(sharedMemPath string, size int64, initQueue bool) (fd int, err error) {
|
|
var tmpDir = DefaultTmpDir
|
|
if sharedMemPath != "" {
|
|
tmpDir = sharedMemPath
|
|
}
|
|
f, err := os.CreateTemp(tmpDir, "sharedmem_test")
|
|
if err != nil {
|
|
return -1, fmt.Errorf("TempFile failed: %v", err)
|
|
}
|
|
defer f.Close()
|
|
unix.Unlink(f.Name())
|
|
|
|
if initQueue {
|
|
// Write the "slot-free" flag in the initial queue.
|
|
if _, err := f.WriteAt([]byte{0, 0, 0, 0, 0, 0, 0, 0x80}, 0); err != nil {
|
|
return -1, fmt.Errorf("WriteAt failed: %v", err)
|
|
}
|
|
}
|
|
|
|
fd, err = unix.Dup(int(f.Fd()))
|
|
if err != nil {
|
|
return -1, fmt.Errorf("unix.Dup(%d) failed: %v", f.Fd(), err)
|
|
}
|
|
|
|
if err := unix.Ftruncate(fd, size); err != nil {
|
|
unix.Close(fd)
|
|
return -1, fmt.Errorf("ftruncate(%d, %d) failed: %v", fd, size, err)
|
|
}
|
|
|
|
return fd, nil
|
|
}
|
|
|
|
func closeFDs(c QueueConfig) {
|
|
unix.Close(c.DataFD)
|
|
c.EventFD.Close()
|
|
unix.Close(c.TxPipeFD)
|
|
unix.Close(c.RxPipeFD)
|
|
unix.Close(c.SharedDataFD)
|
|
}
|