Automated rollback of changelist 629815554

PiperOrigin-RevId: 630263974
This commit is contained in:
Nicolas Lacasse
2024-05-02 21:02:05 -07:00
committed by gVisor bot
parent 283b71c3ba
commit f67e10cf38
12 changed files with 102 additions and 370 deletions
-12
View File
@@ -346,18 +346,6 @@ func (b IPv4) DestinationAddress() tcpip.Address {
return tcpip.AddrFrom4([4]byte(b[dstAddr : dstAddr+IPv4AddressSize]))
}
// SourceAddressSlice returns the "source address" field of the IPv4 header as a
// byte slice.
func (b IPv4) SourceAddressSlice() []byte {
return []byte(b[srcAddr : srcAddr+IPv4AddressSize])
}
// DestinationAddressSlice returns the "destination address" field of the IPv4
// header as a byte slice.
func (b IPv4) DestinationAddressSlice() []byte {
return []byte(b[dstAddr : dstAddr+IPv4AddressSize])
}
// SetSourceAddressWithChecksumUpdate implements ChecksummableNetwork.
func (b IPv4) SetSourceAddressWithChecksumUpdate(new tcpip.Address) {
b.SetChecksum(^checksumUpdate2ByteAlignedAddress(^b.Checksum(), b.SourceAddress(), new))
-12
View File
@@ -225,18 +225,6 @@ func (b IPv6) DestinationAddress() tcpip.Address {
return tcpip.AddrFrom16([16]byte(b[v6DstAddr:][:IPv6AddressSize]))
}
// SourceAddressSlice returns the "source address" field of the ipv6 header as a
// byte slice.
func (b IPv6) SourceAddressSlice() []byte {
return []byte(b[v6SrcAddr:][:IPv6AddressSize])
}
// DestinationAddressSlice returns the "destination address" field of the ipv6
// header as a byte slice.
func (b IPv6) DestinationAddressSlice() []byte {
return []byte(b[v6DstAddr:][:IPv6AddressSize])
}
// Checksum implements Network.Checksum. Given that IPv6 doesn't have a
// checksum, it just returns 0.
func (IPv6) Checksum() uint16 {
-4
View File
@@ -14,17 +14,13 @@ go_library(
"mmap_stub.go",
"mmap_unsafe.go",
"packet_dispatchers.go",
"processors.go",
],
visibility = ["//visibility:public"],
deps = [
"//pkg/atomicbitops",
"//pkg/buffer",
"//pkg/rand",
"//pkg/sleep",
"//pkg/sync",
"//pkg/tcpip",
"//pkg/tcpip/hash/jenkins",
"//pkg/tcpip/header",
"//pkg/tcpip/link/rawfile",
"//pkg/tcpip/link/stopfd",
+2 -6
View File
@@ -42,7 +42,6 @@ package fdbased
import (
"fmt"
"runtime"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/atomicbitops"
@@ -323,9 +322,6 @@ func New(opts *Options) (stack.LinkEndpoint, error) {
e.gsoMaxSize = opts.GSOMaxSize
}
}
if opts.ProcessorsPerChannel == 0 {
opts.ProcessorsPerChannel = max(1, runtime.GOMAXPROCS(0)/len(opts.FDs))
}
inboundDispatcher, err := createInboundDispatcher(e, fd, isSocket, fid, opts)
if err != nil {
@@ -340,7 +336,7 @@ func New(opts *Options) (stack.LinkEndpoint, error) {
func createInboundDispatcher(e *endpoint, fd int, isSocket bool, fID int32, opts *Options) (linkDispatcher, error) {
// By default use the readv() dispatcher as it works with all kinds of
// FDs (tap/tun/unix domain sockets and af_packet).
inboundDispatcher, err := newReadVDispatcher(fd, e, opts)
inboundDispatcher, err := newReadVDispatcher(fd, e)
if err != nil {
return nil, fmt.Errorf("newReadVDispatcher(%d, %+v) = %v", fd, e, err)
}
@@ -380,7 +376,7 @@ func createInboundDispatcher(e *endpoint, fd int, isSocket bool, fID int32, opts
switch e.packetDispatchMode {
case PacketMMap:
inboundDispatcher, err = newPacketMMapDispatcher(fd, e, opts)
inboundDispatcher, err = newPacketMMapDispatcher(fd, e)
if err != nil {
return nil, fmt.Errorf("newPacketMMapDispatcher(%d, %+v) = %v", fd, e, err)
}
+3 -3
View File
@@ -582,7 +582,7 @@ func (*fakeNetworkDispatcher) DeliverLinkPacket(tcpip.NetworkProtocolNumber, *st
func TestDispatchPacketFormat(t *testing.T) {
for _, test := range []struct {
name string
newDispatcher func(fd int, e *endpoint, opts *Options) (linkDispatcher, error)
newDispatcher func(fd int, e *endpoint) (linkDispatcher, error)
}{
{
name: "readVDispatcher",
@@ -590,7 +590,7 @@ func TestDispatchPacketFormat(t *testing.T) {
},
{
name: "recvMMsgDispatcher",
newDispatcher: newRecvMMsgDispatcher,
newDispatcher: func(fd int, e *endpoint) (linkDispatcher, error) { return newRecvMMsgDispatcher(fd, e, &Options{}) },
},
} {
t.Run(test.name, func(t *testing.T) {
@@ -618,7 +618,7 @@ func TestDispatchPacketFormat(t *testing.T) {
d, err := test.newDispatcher(fds[0], &endpoint{
hdrSize: header.EthernetMinimumSize,
dispatcher: sink,
}, &Options{ProcessorsPerChannel: 1})
})
if err != nil {
t.Fatal(err)
}
+38 -33
View File
@@ -130,17 +130,11 @@ type packetMMapDispatcher struct {
// ringOffset is the current offset into the ring buffer where the next
// inbound packet will be placed by the kernel.
ringOffset int
// mgr is the processor goroutine manager.
mgr *processorManager
}
func (d *packetMMapDispatcher) release() {
d.mgr.close()
}
func (*packetMMapDispatcher) release() {}
func (d *packetMMapDispatcher) readMMappedPackets() (stack.PacketBufferList, bool, tcpip.Error) {
var pkts stack.PacketBufferList
func (d *packetMMapDispatcher) readMMappedPacket() (*buffer.View, bool, tcpip.Error) {
hdr := tPacketHdr(d.ringBuffer[d.ringOffset*tpFrameSize:])
for hdr.tpStatus()&tpStatusUser == 0 {
stopped, errno := rawfile.BlockingPollUntilStopped(d.EFD, d.fd, unix.POLLIN|unix.POLLERR)
@@ -148,10 +142,10 @@ func (d *packetMMapDispatcher) readMMappedPackets() (stack.PacketBufferList, boo
if errno == unix.EINTR {
continue
}
return pkts, stopped, rawfile.TranslateErrno(errno)
return nil, stopped, rawfile.TranslateErrno(errno)
}
if stopped {
return pkts, true, nil
return nil, true, nil
}
if hdr.tpStatus()&tpStatusCopy != 0 {
// This frame is truncated so skip it after flipping the
@@ -163,39 +157,50 @@ func (d *packetMMapDispatcher) readMMappedPackets() (stack.PacketBufferList, boo
}
}
for hdr.tpStatus()&tpStatusUser == 1 {
// Copy out the packet from the mmapped frame to a locally owned buffer.
pkts.PushBack(stack.NewPacketBuffer(stack.PacketBufferOptions{
Payload: buffer.MakeWithView(buffer.NewViewWithData(hdr.Payload())),
}))
// Release packet to kernel.
hdr.setTPStatus(tpStatusKernel)
d.ringOffset = (d.ringOffset + 1) % tpFrameNR
hdr = tPacketHdr(d.ringBuffer[d.ringOffset*tpFrameSize:])
}
return pkts, false, nil
// Copy out the packet from the mmapped frame to a locally owned buffer.
pkt := buffer.NewView(int(hdr.tpSnapLen()))
pkt.Write(hdr.Payload())
// Release packet to kernel.
hdr.setTPStatus(tpStatusKernel)
d.ringOffset = (d.ringOffset + 1) % tpFrameNR
return pkt, false, nil
}
// dispatch reads packets from an mmaped ring buffer and dispatches them to the
// network stack.
func (d *packetMMapDispatcher) dispatch() (bool, tcpip.Error) {
pkts, stopped, err := d.readMMappedPackets()
defer pkts.Reset()
pkt, stopped, err := d.readMMappedPacket()
if err != nil || stopped {
return false, err
}
for _, pkt := range pkts.AsSlice() {
if d.e.hdrSize > 0 {
hdr, ok := pkt.LinkHeader().Consume(d.e.hdrSize)
if !ok {
panic(fmt.Sprintf("LinkHeader().Consume(%d) must succeed", d.e.hdrSize))
}
pkt.NetworkProtocolNumber = header.Ethernet(hdr).Type()
var p tcpip.NetworkProtocolNumber
if d.e.hdrSize > 0 {
p = header.Ethernet(pkt.AsSlice()).Type()
} else {
// We don't get any indication of what the packet is, so try to guess
// if it's an IPv4 or IPv6 packet.
switch header.IPVersion(pkt.AsSlice()) {
case header.IPv4Version:
p = header.IPv4ProtocolNumber
case header.IPv6Version:
p = header.IPv6ProtocolNumber
default:
return true, nil
}
d.mgr.queuePacket(pkt, d.e.hdrSize > 0)
}
if pkts.Len() > 0 {
d.mgr.wakeReady()
pbuf := stack.NewPacketBuffer(stack.PacketBufferOptions{
Payload: buffer.MakeWithView(pkt),
})
defer pbuf.DecRef()
if d.e.hdrSize > 0 {
if _, ok := pbuf.LinkHeader().Consume(d.e.hdrSize); !ok {
panic(fmt.Sprintf("LinkHeader().Consume(%d) must succeed", d.e.hdrSize))
}
}
d.e.mu.RLock()
dsp := d.e.dispatcher
d.e.mu.RUnlock()
dsp.DeliverNetworkPacket(p, pbuf)
return true, nil
}
+1 -1
View File
@@ -19,6 +19,6 @@ package fdbased
// Stubbed out version for non-linux/non-amd64/non-arm64 platforms.
func newPacketMMapDispatcher(fd int, e *endpoint, opts *Options) (linkDispatcher, error) {
func newPacketMMapDispatcher(fd int, e *endpoint) (linkDispatcher, error) {
return nil, nil
}
+1 -3
View File
@@ -47,7 +47,7 @@ func (t tPacketHdr) setTPStatus(status uint32) {
(*atomicbitops.Uint32)(statusPtr).Store(status)
}
func newPacketMMapDispatcher(fd int, e *endpoint, opts *Options) (linkDispatcher, error) {
func newPacketMMapDispatcher(fd int, e *endpoint) (linkDispatcher, error) {
stopFD, err := stopfd.New()
if err != nil {
return nil, err
@@ -77,8 +77,6 @@ func newPacketMMapDispatcher(fd int, e *endpoint, opts *Options) (linkDispatcher
if err != nil {
return nil, fmt.Errorf("unix.Mmap(...,0, %v, ...) failed = %v", sz, err)
}
d.mgr = newProcessorManager(opts, e)
d.mgr.start()
d.ringBuffer = buf
return d, nil
}
+56 -20
View File
@@ -155,12 +155,9 @@ type readVDispatcher struct {
// buf is the iovec buffer that contains the packet contents.
buf *iovecBuffer
// mgr is the processor goroutine manager.
mgr *processorManager
}
func newReadVDispatcher(fd int, e *endpoint, opts *Options) (linkDispatcher, error) {
func newReadVDispatcher(fd int, e *endpoint) (linkDispatcher, error) {
stopFD, err := stopfd.New()
if err != nil {
return nil, err
@@ -172,14 +169,11 @@ func newReadVDispatcher(fd int, e *endpoint, opts *Options) (linkDispatcher, err
}
skipsVnetHdr := d.e.gsoKind == stack.HostGSOSupported
d.buf = newIovecBuffer(BufConfig, skipsVnetHdr)
d.mgr = newProcessorManager(opts, e)
d.mgr.start()
return d, nil
}
func (d *readVDispatcher) release() {
d.buf.release()
d.mgr.close()
}
// dispatch reads one packet from the file descriptor and dispatches it.
@@ -194,14 +188,35 @@ func (d *readVDispatcher) dispatch() (bool, tcpip.Error) {
})
defer pkt.DecRef()
var p tcpip.NetworkProtocolNumber
if d.e.hdrSize > 0 {
if !d.e.parseHeader(pkt) {
return false, nil
}
pkt.NetworkProtocolNumber = header.Ethernet(pkt.LinkHeader().Slice()).Type()
p = header.Ethernet(pkt.LinkHeader().Slice()).Type()
} else {
// We don't get any indication of what the packet is, so try to guess
// if it's an IPv4 or IPv6 packet.
// IP version information is at the first octet, so pulling up 1 byte.
h, ok := pkt.Data().PullUp(1)
if !ok {
return true, nil
}
switch header.IPVersion(h) {
case header.IPv4Version:
p = header.IPv4ProtocolNumber
case header.IPv6Version:
p = header.IPv6ProtocolNumber
default:
return true, nil
}
}
d.mgr.queuePacket(pkt, d.e.hdrSize > 0)
d.mgr.wakeReady()
d.e.mu.RLock()
dsp := d.e.dispatcher
d.e.mu.RUnlock()
dsp.DeliverNetworkPacket(p, pkt)
return true, nil
}
@@ -229,9 +244,6 @@ type recvMMsgDispatcher struct {
// gro coalesces incoming packets to increase throughput.
gro gro.GRO
// mgr is the processor goroutine manager.
mgr *processorManager
}
const (
@@ -257,8 +269,6 @@ func newRecvMMsgDispatcher(fd int, e *endpoint, opts *Options) (linkDispatcher,
d.bufs[i] = newIovecBuffer(BufConfig, skipsVnetHdr)
}
d.gro.Init(opts.GRO)
d.mgr = newProcessorManager(opts, e)
d.mgr.start()
return d, nil
}
@@ -267,7 +277,6 @@ func (d *recvMMsgDispatcher) release() {
for _, iov := range d.bufs {
iov.release()
}
d.mgr.close()
}
// recvMMsgDispatch reads more than one packet at a time from the file
@@ -309,17 +318,44 @@ func (d *recvMMsgDispatcher) dispatch() (bool, tcpip.Error) {
// Mark that this iovec has been processed.
d.msgHdrs[k].Msg.Iovlen = 0
var p tcpip.NetworkProtocolNumber
if d.e.hdrSize > 0 {
hdr, ok := pkt.LinkHeader().Consume(d.e.hdrSize)
if !ok {
return false, nil
}
pkt.NetworkProtocolNumber = header.Ethernet(hdr).Type()
p = header.Ethernet(hdr).Type()
} else {
// We don't get any indication of what the packet is, so try to guess
// if it's an IPv4 or IPv6 packet.
// IP version information is at the first octet, so pulling up 1 byte.
h, ok := pkt.Data().PullUp(1)
if !ok {
// Skip this packet.
continue
}
switch header.IPVersion(h) {
case header.IPv4Version:
p = header.IPv4ProtocolNumber
case header.IPv6Version:
p = header.IPv6ProtocolNumber
default:
// Skip this packet.
continue
}
}
// Only use GRO if there's more than one packet.
if nMsgs > 1 {
pkt.NetworkProtocolNumber = p
pkt.RXChecksumValidated = d.e.caps&stack.CapabilityRXChecksumOffload != 0
d.gro.Enqueue(pkt)
} else {
dsp.DeliverNetworkPacket(p, pkt)
return true, nil
}
pkt.RXChecksumValidated = d.e.caps&stack.CapabilityRXChecksumOffload != 0
d.mgr.queuePacket(pkt, d.e.hdrSize > 0)
}
d.mgr.wakeReady()
d.gro.Flush()
return true, nil
}
-263
View File
@@ -1,263 +0,0 @@
// Copyright 2024 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux
// +build linux
package fdbased
import (
"encoding/binary"
"gvisor.dev/gvisor/pkg/rand"
"gvisor.dev/gvisor/pkg/sleep"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/hash/jenkins"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/stack/gro"
)
type processor struct {
mu sync.Mutex
// +checklocks:mu
pkts stack.PacketBufferList
e *endpoint
gro gro.GRO
sleeper sleep.Sleeper
packetWaker sleep.Waker
closeWaker sleep.Waker
}
func (p *processor) start(wg *sync.WaitGroup) {
defer wg.Done()
defer p.sleeper.Done()
for {
switch w := p.sleeper.Fetch(true); {
case w == &p.packetWaker:
p.deliverPackets()
case w == &p.closeWaker:
p.mu.Lock()
p.pkts.Reset()
p.mu.Unlock()
return
}
}
}
func (p *processor) deliverPackets() {
p.e.mu.RLock()
p.gro.Dispatcher = p.e.dispatcher
p.e.mu.RUnlock()
p.mu.Lock()
for p.pkts.Len() > 0 {
pkt := p.pkts.PopFront()
p.mu.Unlock()
p.gro.Enqueue(pkt)
pkt.DecRef()
p.mu.Lock()
}
p.mu.Unlock()
p.gro.Flush()
}
// processorManager handles starting, closing, and queuing packets on processor
// goroutines.
type processorManager struct {
processors []processor
seed uint32
wg sync.WaitGroup
e *endpoint
ready []bool
}
// newProcessorManager creates a new processor manager.
func newProcessorManager(opts *Options, e *endpoint) *processorManager {
m := &processorManager{}
m.seed = rand.Uint32()
m.ready = make([]bool, opts.ProcessorsPerChannel)
m.processors = make([]processor, opts.ProcessorsPerChannel)
m.e = e
m.wg.Add(opts.ProcessorsPerChannel)
for i := range m.processors {
p := &m.processors[i]
p.sleeper.AddWaker(&p.packetWaker)
p.sleeper.AddWaker(&p.closeWaker)
p.gro.Init(opts.GRO)
p.e = e
}
return m
}
// start starts the processor goroutines if the processor manager is configured
// with more than one processor.
func (m *processorManager) start() {
for i := range m.processors {
p := &m.processors[i]
// Only start processor in a separate goroutine if we have multiple of them.
if len(m.processors) > 1 {
go p.start(&m.wg)
}
}
}
func (m *processorManager) connectionHash(cid *connectionID) uint32 {
var payload [4]byte
binary.LittleEndian.PutUint16(payload[0:], cid.srcPort)
binary.LittleEndian.PutUint16(payload[2:], cid.dstPort)
h := jenkins.Sum32(m.seed)
h.Write(payload[:])
h.Write(cid.srcAddr)
h.Write(cid.dstAddr)
return h.Sum32()
}
// queuePacket queues a packet to be delivered to the appropriate processor.
func (m *processorManager) queuePacket(pkt *stack.PacketBuffer, hasEthHeader bool) {
var pIdx int
if len(m.processors) > 1 {
cid, nonConnectionPkt := tcpipConnectionID(pkt)
if !hasEthHeader {
if nonConnectionPkt {
// If there's no eth header this should be a standard tcpip packet. If
// it isn't the packet is invalid so drop it.
return
}
pkt.NetworkProtocolNumber = cid.proto
}
// If the packet is not associated with an active connection, use the
// first processor.
if nonConnectionPkt {
pIdx = 0
} else {
pIdx = int(m.connectionHash(&cid)) % len(m.processors)
}
} else {
pIdx = 0
}
p := &m.processors[pIdx]
p.mu.Lock()
defer p.mu.Unlock()
pkt.IncRef()
p.pkts.PushBack(pkt)
m.ready[pIdx] = true
}
type connectionID struct {
proto tcpip.NetworkProtocolNumber
srcAddr, dstAddr []byte
srcPort, dstPort uint16
}
// tcpipConnectionID returns a tcpip connection id tuple based on the data found
// in the packet. It returns true if the packet is not associated with an active
// connection (e.g ARP, NDP, etc). The method assumes link headers have already
// been processed if they were present.
func tcpipConnectionID(pkt *stack.PacketBuffer) (connectionID, bool) {
var cid connectionID
h, ok := pkt.Data().PullUp(1)
if !ok {
// Skip this packet.
return cid, true
}
const tcpSrcDstPortLen = 4
switch header.IPVersion(h) {
case header.IPv4Version:
hdrLen := header.IPv4(h).HeaderLength()
h, ok = pkt.Data().PullUp(int(hdrLen) + tcpSrcDstPortLen)
if !ok {
return cid, true
}
ipHdr := header.IPv4(h[:hdrLen])
tcpHdr := header.TCP(h[hdrLen:][:tcpSrcDstPortLen])
cid.srcAddr = ipHdr.SourceAddressSlice()
cid.dstAddr = ipHdr.DestinationAddressSlice()
cid.srcPort = tcpHdr.SourcePort()
cid.dstPort = tcpHdr.DestinationPort()
cid.proto = header.IPv4ProtocolNumber
case header.IPv6Version:
h, ok = pkt.Data().PullUp(header.IPv6FixedHeaderSize + tcpSrcDstPortLen)
if !ok {
return cid, true
}
ipHdr := header.IPv6(h)
var tcpHdr header.TCP
if tcpip.TransportProtocolNumber(ipHdr.NextHeader()) == header.TCPProtocolNumber {
tcpHdr = header.TCP(h[header.IPv6FixedHeaderSize:][:tcpSrcDstPortLen])
} else {
// Slow path for IPv6 options :(.
dataBuf := pkt.Data().ToBuffer()
dataBuf.TrimFront(header.IPv6MinimumSize)
it := header.MakeIPv6PayloadIterator(header.IPv6ExtensionHeaderIdentifier(ipHdr.NextHeader()), dataBuf)
defer it.Release()
for {
_, done, err := it.Next()
if done || err != nil {
break
}
}
h, ok = pkt.Data().PullUp(int(it.HeaderOffset()) + tcpSrcDstPortLen)
if !ok {
return cid, true
}
tcpHdr = header.TCP(h[it.HeaderOffset():][:tcpSrcDstPortLen])
}
cid.srcAddr = ipHdr.SourceAddressSlice()
cid.dstAddr = ipHdr.DestinationAddressSlice()
cid.srcPort = tcpHdr.SourcePort()
cid.dstPort = tcpHdr.DestinationPort()
cid.proto = header.IPv6ProtocolNumber
default:
return cid, true
}
return cid, false
}
func (m *processorManager) close() {
if len(m.processors) < 2 {
return
}
for i := range m.processors {
p := &m.processors[i]
p.closeWaker.Assert()
}
}
// wakeReady wakes up all processors that have a packet queued. If there is only
// one processor, the method delivers the packet inline without waking a
// goroutine.
func (m *processorManager) wakeReady() {
for i, ready := range m.ready {
if !ready {
continue
}
p := &m.processors[i]
if len(m.processors) > 1 {
p.packetWaker.Assert()
} else {
p.deliverPackets()
}
m.ready[i] = false
}
}
-12
View File
@@ -62,18 +62,6 @@ func (pl *PacketBufferList) PushBack(pb *PacketBuffer) {
pl.pbs = append(pl.pbs, pb)
}
// PopFront removes the first element in the list if it exists and returns it.
//
//go:nosplit
func (pl *PacketBufferList) PopFront() *PacketBuffer {
if len(pl.pbs) == 0 {
return nil
}
pkt := pl.pbs[0]
pl.pbs = pl.pbs[1:]
return pkt
}
// DecRef decreases the reference count on each PacketBuffer
// stored in the list.
//
+1 -1
View File
@@ -119,7 +119,7 @@ func RegisterFlags(flagSet *flag.FlagSet) {
flagSet.Bool("rx-checksum-offload", true, "enable RX checksum offload.")
flagSet.Var(queueingDisciplinePtr(QDiscFIFO), "qdisc", "specifies which queueing discipline to apply by default to the non loopback nics used by the sandbox.")
flagSet.Int("num-network-channels", 1, "number of underlying channels(FDs) to use for network link endpoints.")
flagSet.Int("network-processors-per-channel", 0, "number of goroutines in each channel for processng inbound packets. If 0, the link endpoint will divide GOMAXPROCS evenly among the number of channels specified by num-network-channels.")
flagSet.Int("network-processors-per-channel", 1, "number of goroutines in each channel for processng inbound packets. If 0, the link endpoint will divide GOMAXPROCS evenly among the number of channels specified by num-network-channels.")
flagSet.Bool("buffer-pooling", true, "DEPRECATED: this flag has no effect. Buffer pooling is always enabled.")
flagSet.Var(&xdpConfig, "EXPERIMENTAL-xdp", `whether and how to use XDP. Can be one of: "off" (default), "ns", "redirect:<device name>", or "tunnel:<device name>"`)
flagSet.Bool("EXPERIMENTAL-xdp-need-wakeup", true, "EXPERIMENTAL. Use XDP_USE_NEED_WAKEUP with XDP sockets.") // TODO(b/240191988): Figure out whether this helps and remove it as a flag.