github.com/ttpreport/gvisor-ligolo@v0.0.0-20240123134145-a858404967ba/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/ttpreport/gvisor-ligolo/pkg/atomicbitops"
    22  	"github.com/ttpreport/gvisor-ligolo/pkg/sleep"
    23  	"github.com/ttpreport/gvisor-ligolo/pkg/sync"
    24  	"github.com/ttpreport/gvisor-ligolo/pkg/tcpip"
    25  	"github.com/ttpreport/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  }