mirror of
https://github.com/slackhq/nebula.git
synced 2026-11-15 14:36:52 +01:00
Pick multiport lanes by inside flow hash, not routine index
Lanes were collapsing onto lane 0 and staying there. Nebula writes a peer's inbound packets to the tun queue matching the socket they arrived on, and tun_flow_update teaches the kernel to steer that flow's outbound packets to the same queue, preferring what it learned over the hash. So while a tunnel's lanes were still down -- which is every tunnel, for its first moments -- all traffic arrived on socket 0, pinning every flow to queue 0 on both hosts for as long as it stayed busy. With the lane following the routine, that meant lane 0 forever. Two changes break the loop: Choose the lane from the packet's own 5-tuple hash, so lane spread no longer depends on tun steering at all. The hash is symmetric, and the high-addressed side rotates its choice by the low side's port offset, so a flow's two directions land on partner lanes with exact reverse 4-tuples and each arrives through the conntrack entry the other's probe opened. Seed every lane as demanded at lane-set creation, so the first traffic tick probes them all at once rather than waiting for a flow to hash onto each. Lanes have to be up before the flows are, not after. A routine may now send on any lane, so it holds a SendBatch per lane, built lazily and all borrowing one shared arena -- the slab is the expensive part, and a batch per (routine, lane) would otherwise cost hundreds of megabytes. full() counts across the batches so the total outstanding stays bounded as before. That also means several routines can write to one socket, which was already true for base traffic under multiport: the linux batchWriter's sendmmsg scratch had no lock. Take a mutex there, once per flush.
This commit is contained in:
1 parent
4936b40169
commit
53c565eb29
7 files changed
+470
-148
No files matched your search
@@ -12,6 +12,7 @@ import (
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
@@ -19,8 +20,14 @@ import (
|
||||
|
||||
// batchWriter owns the sendmmsg(2)/UDP-GSO transmit path for a StdConn: the
|
||||
// scratch WriteBatch packs mmsghdr entries into, plus the GSO capability
|
||||
// state probed at socket creation. Each queue has its own StdConn and
|
||||
// batchWriter, so no locking is needed.
|
||||
// state probed at socket creation.
|
||||
//
|
||||
// One socket can have several senders: under multiport every routine writes its
|
||||
// base-session traffic to socket 0, and any routine may write to any lane
|
||||
// socket, since a packet's lane comes from its flow rather than from the routine
|
||||
// that read it. The scratch is per socket, not per sender, so mu serializes
|
||||
// packing and draining. It is taken once per flush — a syscall's worth of work —
|
||||
// and only contends when two routines flush the same socket at the same instant.
|
||||
//
|
||||
// Terminology, smallest to largest:
|
||||
//
|
||||
@@ -42,6 +49,10 @@ type batchWriter struct {
|
||||
fd int
|
||||
isV4 bool
|
||||
|
||||
// mu guards everything below it: the scratch, and the GSO state WriteBatch
|
||||
// can clear.
|
||||
mu sync.Mutex
|
||||
|
||||
// UDP GSO (sendmsg with UDP_SEGMENT cmsg) support, probed once at
|
||||
// socket creation and cleared by WriteBatch if the kernel later rejects
|
||||
// a GSO send (the setsockopt probe cannot see per-route limitations).
|
||||
@@ -196,6 +207,9 @@ func (w *batchWriter) WriteBatch(bufs [][]byte, addrs []netip.AddrPort) (int, er
|
||||
return 0, fmt.Errorf("WriteBatch: len(bufs)=%d != len(addrs)=%d", len(bufs), len(addrs))
|
||||
}
|
||||
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
// A destination the kernel rejects results in us dropping that entry (one packet, or one same-destination GSO run).
|
||||
// We count what actually made it out rather than returning an error.
|
||||
written := 0
|
||||
|
||||
Reference in new issue
Block a user