github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/tcpip/link/fdbased/packet_dispatchers.go (about) 1 // Copyright 2018 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 fdbased 19 20 import ( 21 "golang.org/x/sys/unix" 22 "github.com/nicocha30/gvisor-ligolo/pkg/buffer" 23 "github.com/nicocha30/gvisor-ligolo/pkg/tcpip" 24 "github.com/nicocha30/gvisor-ligolo/pkg/tcpip/header" 25 "github.com/nicocha30/gvisor-ligolo/pkg/tcpip/link/rawfile" 26 "github.com/nicocha30/gvisor-ligolo/pkg/tcpip/link/stopfd" 27 "github.com/nicocha30/gvisor-ligolo/pkg/tcpip/stack" 28 ) 29 30 // BufConfig defines the shape of the buffer used to read packets from the NIC. 31 var BufConfig = []int{128, 256, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768} 32 33 type iovecBuffer struct { 34 // buffer is the actual buffer that holds the packet contents. Some contents 35 // are reused across calls to pullBuffer if number of requested bytes is 36 // smaller than the number of bytes allocated in the buffer. 37 views []*buffer.View 38 39 // iovecs are initialized with base pointers/len of the corresponding 40 // entries in the views defined above, except when GSO is enabled 41 // (skipsVnetHdr) then the first iovec points to a buffer for the vnet header 42 // which is stripped before the views are passed up the stack for further 43 // processing. 44 iovecs []unix.Iovec 45 46 // sizes is an array of buffer sizes for the underlying views. sizes is 47 // immutable. 48 sizes []int 49 50 // skipsVnetHdr is true if virtioNetHdr is to skipped. 51 skipsVnetHdr bool 52 53 // pulledIndex is the index of the last []byte buffer pulled from the 54 // underlying buffer storage during a call to pullBuffers. It is -1 55 // if no buffer is pulled. 56 pulledIndex int 57 } 58 59 func newIovecBuffer(sizes []int, skipsVnetHdr bool) *iovecBuffer { 60 b := &iovecBuffer{ 61 views: make([]*buffer.View, len(sizes)), 62 sizes: sizes, 63 skipsVnetHdr: skipsVnetHdr, 64 } 65 niov := len(b.views) 66 if b.skipsVnetHdr { 67 niov++ 68 } 69 b.iovecs = make([]unix.Iovec, niov) 70 return b 71 } 72 73 func (b *iovecBuffer) nextIovecs() []unix.Iovec { 74 vnetHdrOff := 0 75 if b.skipsVnetHdr { 76 var vnetHdr [virtioNetHdrSize]byte 77 // The kernel adds virtioNetHdr before each packet, but 78 // we don't use it, so we allocate a buffer for it, 79 // add it in iovecs but don't add it in a view. 80 b.iovecs[0] = unix.Iovec{Base: &vnetHdr[0]} 81 b.iovecs[0].SetLen(virtioNetHdrSize) 82 vnetHdrOff++ 83 } 84 85 for i := range b.views { 86 if b.views[i] != nil { 87 break 88 } 89 v := buffer.NewViewSize(b.sizes[i]) 90 b.views[i] = v 91 b.iovecs[i+vnetHdrOff] = unix.Iovec{Base: v.BasePtr()} 92 b.iovecs[i+vnetHdrOff].SetLen(v.Size()) 93 } 94 return b.iovecs 95 } 96 97 // pullBuffer extracts the enough underlying storage from b.buffer to hold n 98 // bytes. It removes this storage from b.buffer, returns a new buffer 99 // that holds the storage, and updates pulledIndex to indicate which part 100 // of b.buffer's storage must be reallocated during the next call to 101 // nextIovecs. 102 func (b *iovecBuffer) pullBuffer(n int) buffer.Buffer { 103 var views []*buffer.View 104 c := 0 105 if b.skipsVnetHdr { 106 c += virtioNetHdrSize 107 if c >= n { 108 // Nothing in the packet. 109 return buffer.Buffer{} 110 } 111 } 112 // Remove the used views from the buffer. 113 for i, v := range b.views { 114 c += v.Size() 115 if c >= n { 116 b.views[i].CapLength(v.Size() - (c - n)) 117 views = append(views, b.views[:i+1]...) 118 break 119 } 120 } 121 for i := range views { 122 b.views[i] = nil 123 } 124 if b.skipsVnetHdr { 125 // Exclude the size of the vnet header. 126 n -= virtioNetHdrSize 127 } 128 pulled := buffer.Buffer{} 129 for _, v := range views { 130 pulled.Append(v) 131 } 132 pulled.Truncate(int64(n)) 133 return pulled 134 } 135 136 func (b *iovecBuffer) release() { 137 for _, v := range b.views { 138 if v != nil { 139 v.Release() 140 v = nil 141 } 142 } 143 } 144 145 // readVDispatcher uses readv() system call to read inbound packets and 146 // dispatches them. 147 type readVDispatcher struct { 148 stopfd.StopFD 149 // fd is the file descriptor used to send and receive packets. 150 fd int 151 152 // e is the endpoint this dispatcher is attached to. 153 e *endpoint 154 155 // buf is the iovec buffer that contains the packet contents. 156 buf *iovecBuffer 157 } 158 159 func newReadVDispatcher(fd int, e *endpoint) (linkDispatcher, error) { 160 stopFD, err := stopfd.New() 161 if err != nil { 162 return nil, err 163 } 164 d := &readVDispatcher{ 165 StopFD: stopFD, 166 fd: fd, 167 e: e, 168 } 169 skipsVnetHdr := d.e.gsoKind == stack.HostGSOSupported 170 d.buf = newIovecBuffer(BufConfig, skipsVnetHdr) 171 return d, nil 172 } 173 174 func (d *readVDispatcher) release() { 175 d.buf.release() 176 } 177 178 // dispatch reads one packet from the file descriptor and dispatches it. 179 func (d *readVDispatcher) dispatch() (bool, tcpip.Error) { 180 n, err := rawfile.BlockingReadvUntilStopped(d.EFD, d.fd, d.buf.nextIovecs()) 181 if n <= 0 || err != nil { 182 return false, err 183 } 184 185 pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ 186 Payload: d.buf.pullBuffer(n), 187 }) 188 defer pkt.DecRef() 189 190 var p tcpip.NetworkProtocolNumber 191 if d.e.hdrSize > 0 { 192 if !d.e.parseHeader(pkt) { 193 return false, nil 194 } 195 p = header.Ethernet(pkt.LinkHeader().Slice()).Type() 196 } else { 197 // We don't get any indication of what the packet is, so try to guess 198 // if it's an IPv4 or IPv6 packet. 199 // IP version information is at the first octet, so pulling up 1 byte. 200 h, ok := pkt.Data().PullUp(1) 201 if !ok { 202 return true, nil 203 } 204 switch header.IPVersion(h) { 205 case header.IPv4Version: 206 p = header.IPv4ProtocolNumber 207 case header.IPv6Version: 208 p = header.IPv6ProtocolNumber 209 default: 210 return true, nil 211 } 212 } 213 214 d.e.mu.RLock() 215 dsp := d.e.dispatcher 216 d.e.mu.RUnlock() 217 dsp.DeliverNetworkPacket(p, pkt) 218 219 return true, nil 220 } 221 222 // recvMMsgDispatcher uses the recvmmsg system call to read inbound packets and 223 // dispatches them. 224 type recvMMsgDispatcher struct { 225 stopfd.StopFD 226 // fd is the file descriptor used to send and receive packets. 227 fd int 228 229 // e is the endpoint this dispatcher is attached to. 230 e *endpoint 231 232 // bufs is an array of iovec buffers that contain packet contents. 233 bufs []*iovecBuffer 234 235 // msgHdrs is an array of MMsgHdr objects where each MMsghdr is used to 236 // reference an array of iovecs in the iovecs field defined above. This 237 // array is passed as the parameter to recvmmsg call to retrieve 238 // potentially more than 1 packet per unix. 239 msgHdrs []rawfile.MMsgHdr 240 } 241 242 const ( 243 // MaxMsgsPerRecv is the maximum number of packets we want to retrieve 244 // in a single RecvMMsg call. 245 MaxMsgsPerRecv = 8 246 ) 247 248 func newRecvMMsgDispatcher(fd int, e *endpoint) (linkDispatcher, error) { 249 stopFD, err := stopfd.New() 250 if err != nil { 251 return nil, err 252 } 253 d := &recvMMsgDispatcher{ 254 StopFD: stopFD, 255 fd: fd, 256 e: e, 257 bufs: make([]*iovecBuffer, MaxMsgsPerRecv), 258 msgHdrs: make([]rawfile.MMsgHdr, MaxMsgsPerRecv), 259 } 260 skipsVnetHdr := d.e.gsoKind == stack.HostGSOSupported 261 for i := range d.bufs { 262 d.bufs[i] = newIovecBuffer(BufConfig, skipsVnetHdr) 263 } 264 return d, nil 265 } 266 267 func (d *recvMMsgDispatcher) release() { 268 for _, iov := range d.bufs { 269 iov.release() 270 } 271 } 272 273 // recvMMsgDispatch reads more than one packet at a time from the file 274 // descriptor and dispatches it. 275 func (d *recvMMsgDispatcher) dispatch() (bool, tcpip.Error) { 276 // Fill message headers. 277 for k := range d.msgHdrs { 278 if d.msgHdrs[k].Msg.Iovlen > 0 { 279 break 280 } 281 iovecs := d.bufs[k].nextIovecs() 282 iovLen := len(iovecs) 283 d.msgHdrs[k].Len = 0 284 d.msgHdrs[k].Msg.Iov = &iovecs[0] 285 d.msgHdrs[k].Msg.SetIovlen(iovLen) 286 } 287 288 nMsgs, err := rawfile.BlockingRecvMMsgUntilStopped(d.EFD, d.fd, d.msgHdrs) 289 if nMsgs == -1 || err != nil { 290 return false, err 291 } 292 // Process each of received packets. 293 // Keep a list of packets so we can DecRef outside of the loop. 294 var pkts stack.PacketBufferList 295 296 d.e.mu.RLock() 297 dsp := d.e.dispatcher 298 d.e.mu.RUnlock() 299 300 defer func() { pkts.DecRef() }() 301 for k := 0; k < nMsgs; k++ { 302 n := int(d.msgHdrs[k].Len) 303 pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ 304 Payload: d.bufs[k].pullBuffer(n), 305 }) 306 pkts.PushBack(pkt) 307 308 // Mark that this iovec has been processed. 309 d.msgHdrs[k].Msg.Iovlen = 0 310 311 var p tcpip.NetworkProtocolNumber 312 if d.e.hdrSize > 0 { 313 hdr, ok := pkt.LinkHeader().Consume(d.e.hdrSize) 314 if !ok { 315 return false, nil 316 } 317 p = header.Ethernet(hdr).Type() 318 } else { 319 // We don't get any indication of what the packet is, so try to guess 320 // if it's an IPv4 or IPv6 packet. 321 // IP version information is at the first octet, so pulling up 1 byte. 322 h, ok := pkt.Data().PullUp(1) 323 if !ok { 324 // Skip this packet. 325 continue 326 } 327 switch header.IPVersion(h) { 328 case header.IPv4Version: 329 p = header.IPv4ProtocolNumber 330 case header.IPv6Version: 331 p = header.IPv6ProtocolNumber 332 default: 333 // Skip this packet. 334 continue 335 } 336 } 337 338 dsp.DeliverNetworkPacket(p, pkt) 339 } 340 341 return true, nil 342 }