gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/tcpip/link/sharedmem/queuepair.go (about)

     1  // Copyright 2021 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  //go:build linux
    16  // +build linux
    17  
    18  package sharedmem
    19  
    20  import (
    21  	"fmt"
    22  	"io/ioutil"
    23  
    24  	"golang.org/x/sys/unix"
    25  	"gvisor.dev/gvisor/pkg/eventfd"
    26  )
    27  
    28  const (
    29  	// DefaultQueueDataSize is the size of the shared memory data region that
    30  	// holds the scatter/gather buffers.
    31  	DefaultQueueDataSize = 1 << 20 // 1MiB
    32  
    33  	// DefaultQueuePipeSize is the size of the pipe that holds the packet descriptors.
    34  	//
    35  	// Assuming each packet data is approximately 1280 bytes (IPv6 Minimum MTU)
    36  	// then we can hold approximately 1024*1024/1280 ~ 819 packets in the data
    37  	// area. Which means the pipe needs to be big enough to hold 819
    38  	// descriptors.
    39  	//
    40  	// Each descriptor is approximately 8 (slot descriptor in pipe) +
    41  	// 16 (packet descriptor) + 12 (for buffer descriptor) assuming each packet is
    42  	// stored in exactly 1 buffer descriptor (see queue/tx.go and pipe/tx.go.)
    43  	//
    44  	// Which means we need approximately 36*819 ~ 29 KiB to store all packet
    45  	// descriptors. We could go with a 32 KiB pipe but to give it some slack in
    46  	// how the upper layer may make use of the scatter gather buffers we double
    47  	// this to hold enough descriptors.
    48  	DefaultQueuePipeSize = 64 << 10 // 64KiB
    49  
    50  	// DefaultSharedDataSize is the size of the sharedData region used to
    51  	// enable/disable notifications.
    52  	DefaultSharedDataSize = 4 << 10 // 4KiB
    53  
    54  	// DefaultBufferSize is the size of each individual buffer that the data
    55  	// region is broken down into to hold packet data. Should be larger than
    56  	// 1500 + 14 (Ethernet header) + 10 (VirtIO header) to fit each packet
    57  	// in a single buffer.
    58  	DefaultBufferSize = 2048
    59  
    60  	// DefaultTmpDir is the path used to create the memory files if a path
    61  	// is not provided.
    62  	DefaultTmpDir = "/dev/shm"
    63  )
    64  
    65  // A QueuePair represents a pair of TX/RX queues.
    66  type QueuePair struct {
    67  	// txCfg is the QueueConfig to be used for transmit queue.
    68  	txCfg QueueConfig
    69  
    70  	// rxCfg is the QueueConfig to be used for receive queue.
    71  	rxCfg QueueConfig
    72  }
    73  
    74  // QueueOptions allows queue specific configuration to be specified when
    75  // creating a QueuePair.
    76  type QueueOptions struct {
    77  	// SharedMemPath is the path to use to create the shared memory backing
    78  	// files for the queue.
    79  	//
    80  	// If unspecified it defaults to "/dev/shm".
    81  	SharedMemPath string
    82  }
    83  
    84  // NewQueuePair creates a shared memory QueuePair.
    85  func NewQueuePair(opts QueueOptions) (*QueuePair, error) {
    86  	txCfg, err := createQueueFDs(opts.SharedMemPath, queueSizes{
    87  		dataSize:       DefaultQueueDataSize,
    88  		txPipeSize:     DefaultQueuePipeSize,
    89  		rxPipeSize:     DefaultQueuePipeSize,
    90  		sharedDataSize: DefaultSharedDataSize,
    91  	})
    92  
    93  	if err != nil {
    94  		return nil, fmt.Errorf("failed to create tx queue: %s", err)
    95  	}
    96  
    97  	rxCfg, err := createQueueFDs(opts.SharedMemPath, queueSizes{
    98  		dataSize:       DefaultQueueDataSize,
    99  		txPipeSize:     DefaultQueuePipeSize,
   100  		rxPipeSize:     DefaultQueuePipeSize,
   101  		sharedDataSize: DefaultSharedDataSize,
   102  	})
   103  
   104  	if err != nil {
   105  		closeFDs(txCfg)
   106  		return nil, fmt.Errorf("failed to create rx queue: %s", err)
   107  	}
   108  
   109  	return &QueuePair{
   110  		txCfg: txCfg,
   111  		rxCfg: rxCfg,
   112  	}, nil
   113  }
   114  
   115  // Close closes underlying tx/rx queue fds.
   116  func (q *QueuePair) Close() {
   117  	closeFDs(q.txCfg)
   118  	closeFDs(q.rxCfg)
   119  }
   120  
   121  // TXQueueConfig returns the QueueConfig for the receive queue.
   122  func (q *QueuePair) TXQueueConfig() QueueConfig {
   123  	return q.txCfg
   124  }
   125  
   126  // RXQueueConfig returns the QueueConfig for the transmit queue.
   127  func (q *QueuePair) RXQueueConfig() QueueConfig {
   128  	return q.rxCfg
   129  }
   130  
   131  type queueSizes struct {
   132  	dataSize       int64
   133  	txPipeSize     int64
   134  	rxPipeSize     int64
   135  	sharedDataSize int64
   136  }
   137  
   138  func createQueueFDs(sharedMemPath string, s queueSizes) (QueueConfig, error) {
   139  	success := false
   140  	var eventFD eventfd.Eventfd
   141  	var dataFD, txPipeFD, rxPipeFD, sharedDataFD int
   142  	defer func() {
   143  		if success {
   144  			return
   145  		}
   146  		closeFDs(QueueConfig{
   147  			EventFD:      eventFD,
   148  			DataFD:       dataFD,
   149  			TxPipeFD:     txPipeFD,
   150  			RxPipeFD:     rxPipeFD,
   151  			SharedDataFD: sharedDataFD,
   152  		})
   153  	}()
   154  	eventFD, err := eventfd.Create()
   155  	if err != nil {
   156  		return QueueConfig{}, fmt.Errorf("eventfd failed: %v", err)
   157  	}
   158  	dataFD, err = createFile(sharedMemPath, s.dataSize, false)
   159  	if err != nil {
   160  		return QueueConfig{}, fmt.Errorf("failed to create dataFD: %s", err)
   161  	}
   162  	txPipeFD, err = createFile(sharedMemPath, s.txPipeSize, true)
   163  	if err != nil {
   164  		return QueueConfig{}, fmt.Errorf("failed to create txPipeFD: %s", err)
   165  	}
   166  	rxPipeFD, err = createFile(sharedMemPath, s.rxPipeSize, true)
   167  	if err != nil {
   168  		return QueueConfig{}, fmt.Errorf("failed to create rxPipeFD: %s", err)
   169  	}
   170  	sharedDataFD, err = createFile(sharedMemPath, s.sharedDataSize, false)
   171  	if err != nil {
   172  		return QueueConfig{}, fmt.Errorf("failed to create sharedDataFD: %s", err)
   173  	}
   174  	success = true
   175  	return QueueConfig{
   176  		EventFD:      eventFD,
   177  		DataFD:       dataFD,
   178  		TxPipeFD:     txPipeFD,
   179  		RxPipeFD:     rxPipeFD,
   180  		SharedDataFD: sharedDataFD,
   181  	}, nil
   182  }
   183  
   184  func createFile(sharedMemPath string, size int64, initQueue bool) (fd int, err error) {
   185  	var tmpDir = DefaultTmpDir
   186  	if sharedMemPath != "" {
   187  		tmpDir = sharedMemPath
   188  	}
   189  	f, err := ioutil.TempFile(tmpDir, "sharedmem_test")
   190  	if err != nil {
   191  		return -1, fmt.Errorf("TempFile failed: %v", err)
   192  	}
   193  	defer f.Close()
   194  	unix.Unlink(f.Name())
   195  
   196  	if initQueue {
   197  		// Write the "slot-free" flag in the initial queue.
   198  		if _, err := f.WriteAt([]byte{0, 0, 0, 0, 0, 0, 0, 0x80}, 0); err != nil {
   199  			return -1, fmt.Errorf("WriteAt failed: %v", err)
   200  		}
   201  	}
   202  
   203  	fd, err = unix.Dup(int(f.Fd()))
   204  	if err != nil {
   205  		return -1, fmt.Errorf("unix.Dup(%d) failed: %v", f.Fd(), err)
   206  	}
   207  
   208  	if err := unix.Ftruncate(fd, size); err != nil {
   209  		unix.Close(fd)
   210  		return -1, fmt.Errorf("ftruncate(%d, %d) failed: %v", fd, size, err)
   211  	}
   212  
   213  	return fd, nil
   214  }
   215  
   216  func closeFDs(c QueueConfig) {
   217  	unix.Close(c.DataFD)
   218  	c.EventFD.Close()
   219  	unix.Close(c.TxPipeFD)
   220  	unix.Close(c.RxPipeFD)
   221  	unix.Close(c.SharedDataFD)
   222  }