github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/tcpip/link/qdisc/fifo/fifo.go (about) 1 // Copyright 2020 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 // Package fifo provides the implementation of FIFO queuing discipline that 16 // queues all outbound packets and asynchronously dispatches them to the 17 // lower link endpoint in the order that they were queued. 18 package fifo 19 20 import ( 21 "github.com/nicocha30/gvisor-ligolo/pkg/atomicbitops" 22 "github.com/nicocha30/gvisor-ligolo/pkg/sleep" 23 "github.com/nicocha30/gvisor-ligolo/pkg/sync" 24 "github.com/nicocha30/gvisor-ligolo/pkg/tcpip" 25 "github.com/nicocha30/gvisor-ligolo/pkg/tcpip/stack" 26 ) 27 28 var _ stack.QueueingDiscipline = (*discipline)(nil) 29 30 const ( 31 // BatchSize is the number of packets to write in each syscall. It is 47 32 // because when GvisorGSO is in use then a single 65KB TCP segment can get 33 // split into 46 segments of 1420 bytes and a single 216 byte segment. 34 BatchSize = 47 35 36 qDiscClosed = 1 37 ) 38 39 // discipline represents a QueueingDiscipline which implements a FIFO queue for 40 // all outgoing packets. discipline can have 1 or more underlying 41 // queueDispatchers. All outgoing packets are consistenly hashed to a single 42 // underlying queue using the PacketBuffer.Hash if set, otherwise all packets 43 // are queued to the first queue to avoid reordering in case of missing hash. 44 type discipline struct { 45 wg sync.WaitGroup 46 dispatchers []queueDispatcher 47 48 closed atomicbitops.Int32 49 } 50 51 // queueDispatcher is responsible for dispatching all outbound packets in its 52 // queue. It will also smartly batch packets when possible and write them 53 // through the lower LinkWriter. 54 type queueDispatcher struct { 55 lower stack.LinkWriter 56 57 mu sync.Mutex 58 // +checklocks:mu 59 queue packetBufferCircularList 60 61 newPacketWaker sleep.Waker 62 closeWaker sleep.Waker 63 } 64 65 // New creates a new fifo queuing discipline with the n queues with maximum 66 // capacity of queueLen. 67 // 68 // +checklocksignore: we don't have to hold locks during initialization. 69 func New(lower stack.LinkWriter, n int, queueLen int) stack.QueueingDiscipline { 70 d := &discipline{ 71 dispatchers: make([]queueDispatcher, n), 72 } 73 // Create the required dispatchers 74 for i := range d.dispatchers { 75 qd := &d.dispatchers[i] 76 qd.lower = lower 77 qd.queue.init(queueLen) 78 79 d.wg.Add(1) 80 go func() { 81 defer d.wg.Done() 82 qd.dispatchLoop() 83 }() 84 } 85 return d 86 } 87 88 func (qd *queueDispatcher) dispatchLoop() { 89 s := sleep.Sleeper{} 90 s.AddWaker(&qd.newPacketWaker) 91 s.AddWaker(&qd.closeWaker) 92 defer s.Done() 93 94 var batch stack.PacketBufferList 95 for { 96 switch w := s.Fetch(true); w { 97 case &qd.newPacketWaker: 98 case &qd.closeWaker: 99 qd.mu.Lock() 100 for p := qd.queue.removeFront(); !p.IsNil(); p = qd.queue.removeFront() { 101 p.DecRef() 102 } 103 qd.queue.decRef() 104 qd.mu.Unlock() 105 return 106 default: 107 panic("unknown waker") 108 } 109 qd.mu.Lock() 110 for pkt := qd.queue.removeFront(); !pkt.IsNil(); pkt = qd.queue.removeFront() { 111 batch.PushBack(pkt) 112 if batch.Len() < BatchSize && !qd.queue.isEmpty() { 113 continue 114 } 115 qd.mu.Unlock() 116 _, _ = qd.lower.WritePackets(batch) 117 batch.Reset() 118 qd.mu.Lock() 119 } 120 qd.mu.Unlock() 121 } 122 } 123 124 // WritePacket implements stack.QueueingDiscipline.WritePacket. 125 // 126 // The packet must have the following fields populated: 127 // - pkt.EgressRoute 128 // - pkt.GSOOptions 129 // - pkt.NetworkProtocolNumber 130 func (d *discipline) WritePacket(pkt stack.PacketBufferPtr) tcpip.Error { 131 if d.closed.Load() == qDiscClosed { 132 return &tcpip.ErrClosedForSend{} 133 } 134 qd := &d.dispatchers[int(pkt.Hash)%len(d.dispatchers)] 135 qd.mu.Lock() 136 haveSpace := qd.queue.hasSpace() 137 if haveSpace { 138 qd.queue.pushBack(pkt.IncRef()) 139 } 140 qd.mu.Unlock() 141 if !haveSpace { 142 return &tcpip.ErrNoBufferSpace{} 143 } 144 qd.newPacketWaker.Assert() 145 return nil 146 } 147 148 func (d *discipline) Close() { 149 d.closed.Store(qDiscClosed) 150 for i := range d.dispatchers { 151 d.dispatchers[i].closeWaker.Assert() 152 } 153 d.wg.Wait() 154 }