github.com/noisysockets/netstack@v0.6.0/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 "github.com/noisysockets/netstack/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 }