Files
nebula/lanes_test.go
T
Wade Simmons 11528c6e02 Build multiport lanes lazily, on data-plane demand
Lanes were established for every slot as soon as the base tunnel came up,
so a host with `routines: N` paid N-1 extra Noise handshakes, sessions and
keepalives for every peer it talked to, including peers it exchanges a
trickle with. Only routines that actually carry traffic to a peer need a
lane.

Add a per-slot demand flag to laneState. sendInsideMessage already loads
txLanes[laneSlot] on every direct-path packet; that load missing is now
the signal, and EnsureLanes only starts slots the data plane asked for.
The miss path is unchanged otherwise: the packet rides the base tunnel,
the same fallback used while a lane is down.

noteLaneDemand load-guards its store so repeated misses on a lane-less
slot are plain reads rather than a cache line ping-ponging between the
routines sharing it. The demand check is EnsureLanes' last condition, so
a slot that is pending or inside its backoff keeps the flag for the tick
that can act on it. Consuming the flag also stops a lane whose routine
went quiet from being rebuilt forever after it dies.

The wire format, lane negotiation and port-offset scheme are untouched,
so a lazy node interoperates with an eager one in both directions, and
inbound lane handshakes from a busy peer are still accepted regardless of
our own demand.
2026-09-02 14:48:07 -04:00

588 lines
20 KiB
Go

package nebula
import (
"net/netip"
"testing"
"time"
"github.com/gaissmai/bart"
"github.com/slackhq/nebula/cert"
"github.com/slackhq/nebula/config"
"github.com/slackhq/nebula/header"
"github.com/slackhq/nebula/overlay/batch"
"github.com/slackhq/nebula/overlay/overlaytest"
"github.com/slackhq/nebula/overlay/tio"
"github.com/slackhq/nebula/test"
"github.com/slackhq/nebula/udp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func newTestBaseHostInfo(vpnIp netip.Addr, localIdx, remoteIdx uint32, laneCount int) *HostInfo {
base := &HostInfo{
vpnAddrs: []netip.Addr{vpnIp},
localIndexId: localIdx,
remoteIndexId: remoteIdx,
remotes: NewRemoteList([]netip.Addr{vpnIp}, nil),
HandshakePacket: map[uint8][]byte{},
}
base.SetRemote(netip.MustParseAddrPort("192.0.2.1:4242"))
base.lanes = newLaneState(laneCount, uint16(laneCount), 4242, 0)
return base
}
func newTestLaneHostInfo(base *HostInfo, laneIndex uint16, localIdx, remoteIdx uint32, owned bool) *HostInfo {
lane := &HostInfo{
vpnAddrs: base.vpnAddrs,
localIndexId: localIdx,
remoteIndexId: remoteIdx,
remotes: NewRemoteList(base.vpnAddrs, nil),
HandshakePacket: map[uint8][]byte{},
sockIdx: int(laneIndex),
laneIndex: laneIndex,
laneOwned: owned,
parent: base,
}
lane.SetRemote(netip.MustParseAddrPort("192.0.2.1:4243"))
return lane
}
func TestLanePortOffset(t *testing.T) {
a := netip.MustParseAddr("10.0.0.1")
b := netip.MustParseAddr("10.0.0.2")
// Deterministic and in range.
for _, count := range []uint16{1, 2, 3, 4, 16, 256} {
o := lanePortOffset(a, b, count)
assert.Equal(t, o, lanePortOffset(a, b, count), "count %d not deterministic", count)
assert.Less(t, o, count, "count %d out of range", count)
}
assert.Equal(t, uint16(0), lanePortOffset(a, b, 0), "zero port count")
// The two sides' rotations cancel when port counts match, preserving the
// lane-i-reverses-lane-j conntrack pairing.
for _, count := range []uint16{2, 3, 4, 7, 16} {
for i := range 32 {
peer := netip.AddrFrom4([4]byte{192, 0, 2, byte(i)})
oA := lanePortOffset(a, peer, count)
oB := lanePortOffset(peer, a, count)
assert.Equal(t, uint16(0), (oA+oB)%count,
"offsets don't cancel for peer %s count %d", peer, count)
}
}
// Distinct small peers land on distinct rotations of a big peer's range,
// not all on the same first ports.
const bigPeerPorts = 16
distinct := map[uint16]struct{}{}
for i := range 64 {
client := netip.AddrFrom4([4]byte{192, 0, 2, byte(i)})
distinct[lanePortOffset(client, a, bigPeerPorts)] = struct{}{}
}
assert.GreaterOrEqual(t, len(distinct), 8, "64 clients only produced %d distinct offsets", len(distinct))
}
func TestLaneTargetPort(t *testing.T) {
// No rotation: lane i targets base+i, wrapping past the peer's range.
ls := newLaneState(4, 4, 4242, 0)
for i, want := range map[int]uint16{1: 4243, 2: 4244, 3: 4245, 5: 4243} {
assert.Equal(t, want, ls.laneTargetPort(i), "lane %d", i)
}
// Rotation shifts the whole mapping; the wrapped lane lands on the base
// port itself, which is a valid distinct 4-tuple (our source port differs).
ls = newLaneState(4, 4, 4242, 3)
for i, want := range map[int]uint16{1: 4242, 2: 4243, 3: 4244} {
assert.Equal(t, want, ls.laneTargetPort(i), "rotated lane %d", i)
}
// Fewer peer ports than local lanes: rotation still spreads across all of
// the peer's ports.
ls = newLaneState(16, 2, 4242, 1)
assert.Equal(t, uint16(4242), ls.laneTargetPort(1))
assert.Equal(t, uint16(4243), ls.laneTargetPort(2))
}
func TestLaneHostmapLifecycle(t *testing.T) {
l := test.NewLogger()
hostMap := newHostMap(l)
ifce := &Interface{l: l} // connectionManager nil is tolerated by unlockedAddLane
vpnIp := netip.MustParseAddr("172.1.1.2")
base := newTestBaseHostInfo(vpnIp, 100, 200, 4)
hostMap.Lock()
hostMap.unlockedAddHostInfo(base, ifce)
hostMap.Unlock()
lane := newTestLaneHostInfo(base, 1, 101, 201, true)
hostMap.Lock()
hostMap.unlockedAddLane(lane, ifce)
hostMap.Unlock()
base.lanes.txLanes[1].Store(lane)
// The lane is reachable by index (RX demux, recv_error) but never a Hosts primary.
assert.Equal(t, lane, hostMap.QueryIndex(101))
assert.Equal(t, lane, hostMap.QueryReverseIndex(201))
assert.Equal(t, base, hostMap.Hosts[vpnIp])
// A lane can never be promoted to primary.
hostMap.Lock()
assert.False(t, hostMap.unlockedMakePrimary(lane))
hostMap.Unlock()
assert.Equal(t, base, hostMap.Hosts[vpnIp])
// Deleting the lane clears only its slot, applies backoff, and never
// reports "no more tunnels to peer" (final).
final := hostMap.DeleteHostInfo(lane)
assert.False(t, final)
assert.Nil(t, hostMap.QueryIndex(101))
assert.Equal(t, base, hostMap.Hosts[vpnIp])
assert.Nil(t, base.lanes.txLanes[1].Load())
base.lanes.Lock()
assert.Equal(t, uint8(1), base.lanes.txFails[1])
assert.False(t, base.lanes.txPending[1])
assert.True(t, base.lanes.txRetryAt[1].After(time.Now()))
base.lanes.Unlock()
// Idempotent: deleting again must not bump the backoff further.
hostMap.DeleteHostInfo(lane)
base.lanes.Lock()
assert.Equal(t, uint8(2), base.lanes.txFails[1]) // noteLaneFailure still runs, but slot CAS is a no-op
base.lanes.Unlock()
}
func TestLaneHostmapCascadeDelete(t *testing.T) {
l := test.NewLogger()
hostMap := newHostMap(l)
ifce := &Interface{l: l}
vpnIp := netip.MustParseAddr("172.1.1.3")
base := newTestBaseHostInfo(vpnIp, 300, 400, 4)
hostMap.Lock()
hostMap.unlockedAddHostInfo(base, ifce)
hostMap.Unlock()
owned := newTestLaneHostInfo(base, 1, 301, 401, true)
peer := newTestLaneHostInfo(base, 2, 302, 402, false)
hostMap.Lock()
hostMap.unlockedAddLane(owned, ifce)
hostMap.unlockedAddLane(peer, ifce)
hostMap.Unlock()
base.lanes.txLanes[1].Store(owned)
base.lanes.Lock()
base.lanes.peerLanes = append(base.lanes.peerLanes, peer)
base.lanes.Unlock()
// Deleting the base takes the whole lane family with it.
final := hostMap.DeleteHostInfo(base)
assert.True(t, final)
assert.Nil(t, hostMap.QueryIndex(300))
assert.Nil(t, hostMap.QueryIndex(301))
assert.Nil(t, hostMap.QueryIndex(302))
assert.Nil(t, hostMap.Hosts[vpnIp])
}
// Regression: deleting a hostinfo whose pending entry is NOT the one recorded
// in vpnIps (e.g. a lane, whose vpnAddrs alias the base's) must not evict a
// concurrently pending base handshake for the same address.
func TestHandshakeManagerVpnIpsIdentityDelete(t *testing.T) {
l := test.NewLogger()
hostMap := newHostMap(l)
lh := newTestLighthouse()
hm := NewHandshakeManager(l, hostMap, lh, &udp.NoopConn{}, defaultHandshakeConfig)
vpnIp := netip.MustParseAddr("172.1.1.4")
pendingBase := hm.StartHandshake(vpnIp, nil)
require.NotNil(t, pendingBase)
other := &HostInfo{vpnAddrs: []netip.Addr{vpnIp}, localIndexId: 999}
hm.DeleteHostInfo(other)
// The pending base handshake must still be tracked.
assert.Equal(t, pendingBase, hm.QueryVpnAddr(vpnIp))
// And deleting the actual owner still works.
hm.DeleteHostInfo(pendingBase)
assert.Nil(t, hm.QueryVpnAddr(vpnIp))
}
func newLaneTestConnectionManager(hostMap *HostMap) (*connectionManager, *Interface) {
l := test.NewLogger()
lh := newTestLighthouse()
cs := &CertState{
initiatingVersion: cert.Version1,
privateKey: []byte{},
v1Cert: &dummyCert{version: cert.Version1},
v1Credential: nil,
}
ifce := &Interface{
hostMap: hostMap,
inside: &overlaytest.NoopTun{},
outside: &udp.NoopConn{},
firewall: &Firewall{},
lightHouse: lh,
pki: &PKI{},
handshakeManager: NewHandshakeManager(l, hostMap, lh, &udp.NoopConn{}, defaultHandshakeConfig),
myVpnNetworksTable: new(bart.Lite),
l: l,
}
ifce.pki.cs.Store(cs)
conf := config.NewC(test.NewLogger())
punchy := NewPunchyFromConfig(test.NewLogger(), conf, nil)
cm := newConnectionManagerFromConfig(test.NewLogger(), conf, hostMap, punchy)
cm.intf = ifce
ifce.connectionManager = cm
ifce.handshakeManager.f = ifce
return cm, ifce
}
func TestLaneTrafficDecision(t *testing.T) {
hostMap := newHostMap(test.NewLogger())
cm, ifce := newLaneTestConnectionManager(hostMap)
vpnIp := netip.MustParseAddr("172.1.1.5")
base := newTestBaseHostInfo(vpnIp, 500, 600, 4)
base.ConnectionState = &ConnectionState{}
hostMap.Lock()
hostMap.unlockedAddHostInfo(base, ifce)
hostMap.Unlock()
lane := newTestLaneHostInfo(base, 1, 501, 601, true)
lane.ConnectionState = &ConnectionState{}
hostMap.Lock()
hostMap.unlockedAddLane(lane, ifce)
hostMap.Unlock()
base.lanes.txLanes[1].Store(lane)
now := time.Now()
// A lane with inbound traffic is alive and never swaps primary or
// migrates relays.
lane.markIn()
decision, resolved, _ := cm.makeTrafficDecision(lane.localIndexId, now)
assert.Equal(t, doNothing, decision)
assert.Equal(t, lane, resolved)
assert.False(t, lane.isPendingDeletion())
// An idle lane gets an active keepalive test...
decision, _, _ = cm.makeTrafficDecision(lane.localIndexId, now)
assert.Equal(t, sendTestPacket, decision)
assert.True(t, lane.isPendingDeletion())
// ...and is declared dead when the test goes unanswered.
decision, _, _ = cm.makeTrafficDecision(lane.localIndexId, now)
assert.Equal(t, deleteTunnel, decision)
}
func TestBaseInactiveConsidersLanes(t *testing.T) {
hostMap := newHostMap(test.NewLogger())
cm, _ := newLaneTestConnectionManager(hostMap)
cm.dropInactive.Store(true)
cm.inactivityTimeout.Store(int64(10 * time.Minute))
now := time.Now()
vpnIp := netip.MustParseAddr("172.1.1.6")
base := newTestBaseHostInfo(vpnIp, 700, 800, 4)
base.lastUsed = now.Add(-time.Hour)
// Base alone: inactive.
_, inactive := cm.isInactive(base, now)
assert.True(t, inactive)
// A recently used lane keeps the base alive.
lane := newTestLaneHostInfo(base, 1, 701, 801, true)
lane.lastUsed = now.Add(-time.Minute)
base.lanes.txLanes[1].Store(lane)
_, inactive = cm.isInactive(base, now)
assert.False(t, inactive)
// Peer-owned lanes count too.
base.lanes.txLanes[1].Store(nil)
base.lanes.Lock()
base.lanes.peerLanes = append(base.lanes.peerLanes, lane)
base.lanes.Unlock()
_, inactive = cm.isInactive(base, now)
assert.False(t, inactive)
}
// recordingBatchWriter satisfies batch's writer interface and records what
// was flushed to it.
type recordingBatchWriter struct {
bufs [][]byte
dsts []netip.AddrPort
}
func (w *recordingBatchWriter) WriteBatch(bufs [][]byte, addrs []netip.AddrPort) (int, error) {
for i := range bufs {
w.bufs = append(w.bufs, append([]byte(nil), bufs[i]...))
w.dsts = append(w.dsts, addrs[i])
}
return len(bufs), nil
}
func TestSendInsideMessageLaneSwap(t *testing.T) {
hostMap := newHostMap(test.NewLogger())
cm, ifce := newLaneTestConnectionManager(hostMap)
_ = cm
vpnIp := netip.MustParseAddr("172.1.1.7")
base := newTestBaseHostInfo(vpnIp, 900, 1000, 4)
lane := newTestLaneHostInfo(base, 1, 901, 1001, true)
// Real cipher states from a real handshake so encryption works.
baseInit, _ := runTestHandshake(t)
laneInit, _ := runTestHandshake(t)
var err error
base.ConnectionState, err = newConnectionStateFromResult(baseInit)
require.NoError(t, err)
lane.ConnectionState, err = newConnectionStateFromResult(laneInit)
require.NoError(t, err)
baseWriter := &recordingBatchWriter{}
laneWriter := &recordingBatchWriter{}
newTx := func(laneSlot int) *txQueue {
return &txQueue{
laneSlot: laneSlot,
base: batch.NewSendBatch(baseWriter, batch.SendBatchCap, 1<<16),
lane: batch.NewSendBatch(laneWriter, batch.SendBatchCap, 1<<16),
}
}
tx1 := newTx(1)
tx2 := newTx(2)
pkt := tio.Packet{Bytes: []byte{0x45, 0, 0, 4, 1, 2, 3, 4}}
nb := make([]byte, 12)
// With the lane published, slot-1 traffic uses the lane session and the
// lane batch.
base.lanes.txLanes[1].Store(lane)
ifce.sendInsideMessage(base, pkt, nb, tx1)
tx1.flush(ifce)
require.Len(t, laneWriter.bufs, 1)
require.Empty(t, baseWriter.bufs)
assert.Equal(t, lane.GetRemote(), laneWriter.dsts[0])
h := &header.H{}
require.NoError(t, h.Parse(laneWriter.bufs[0]))
assert.Equal(t, lane.remoteIndexId, h.RemoteIndex)
// An overflow routine sharing slot 1 (multiport.lanes < routines) rides
// the same lane session.
tx1b := newTx(1)
ifce.sendInsideMessage(base, pkt, nb, tx1b)
tx1b.flush(ifce)
require.Len(t, laneWriter.bufs, 2)
require.NoError(t, h.Parse(laneWriter.bufs[1]))
assert.Equal(t, lane.remoteIndexId, h.RemoteIndex)
// Slot 2 has no lane: base tunnel, base batch.
ifce.sendInsideMessage(base, pkt, nb, tx2)
tx2.flush(ifce)
require.Len(t, baseWriter.bufs, 1)
assert.Equal(t, base.GetRemote(), baseWriter.dsts[0])
require.NoError(t, h.Parse(baseWriter.bufs[0]))
assert.Equal(t, base.remoteIndexId, h.RemoteIndex)
// Lane death: slot cleared, instant fallback to base.
base.lanes.txLanes[1].Store(nil)
ifce.sendInsideMessage(base, pkt, nb, tx1)
tx1.flush(ifce)
require.Len(t, baseWriter.bufs, 2)
require.Len(t, laneWriter.bufs, 2)
}
func TestLaneSlotFor(t *testing.T) {
// Overflow routines wrap onto the configured lanes round-robin.
f := &Interface{multiport: true, laneCount: 2}
for i, want := range []int{0, 1, 0, 1, 0, 1} {
assert.Equal(t, want, f.laneSlotFor(i), "routine %d", i)
}
// Full lane count: identity mapping, one lane per routine.
f = &Interface{multiport: true, laneCount: 4}
for i := range 4 {
assert.Equal(t, i, f.laneSlotFor(i), "routine %d", i)
}
// Multiport off: identity, each routine keeps its own writer.
f = &Interface{multiport: false, laneCount: 0}
for i := range 4 {
assert.Equal(t, i, f.laneSlotFor(i), "routine %d", i)
}
}
func TestCompleteLaneResponder(t *testing.T) {
hostMap := newHostMap(test.NewLogger())
_, ifce := newLaneTestConnectionManager(hostMap)
ifce.writers = []udp.Conn{&udp.NoopConn{}, &udp.NoopConn{}, &udp.NoopConn{}, &udp.NoopConn{}}
ifce.messageMetrics = newMessageMetricsOnlyRecvError()
hm := ifce.handshakeManager
hm.config.laneCount = 4
hm.config.lanePortCount = 4
hm.config.laneBasePort = 4242
// A real handshake supplies usable keys and a peer cert.
_, respR := runTestHandshake(t)
respR.PeerLaneIndex = 2
respR.PeerPortCount = 4
respR.PeerBasePort = 5353
via := ViaSender{UdpAddr: netip.MustParseAddrPort("192.0.2.9:5355"), SockIdx: 2}
packet := make([]byte, header.Len+8)
copy(packet[header.Len:], []byte("stage0!!"))
vpnAddrs := []netip.Addr{netip.MustParseAddr("172.1.1.9")}
// No base tunnel: the lane handshake is dropped, nothing registered.
hm.completeLaneResponder(via, packet, []byte("resp"), respR, vpnAddrs)
assert.Nil(t, hostMap.QueryIndex(respR.LocalIndex))
// With a live base the lane attaches to it.
base := newTestBaseHostInfo(vpnAddrs[0], 1300, 1400, 4)
base.ConnectionState = &ConnectionState{}
hostMap.Lock()
hostMap.unlockedAddHostInfo(base, ifce)
hostMap.Unlock()
hm.completeLaneResponder(via, packet, []byte("resp"), respR, vpnAddrs)
lane := hostMap.QueryIndex(respR.LocalIndex)
require.NotNil(t, lane)
assert.True(t, lane.isLane())
assert.False(t, lane.laneOwned)
assert.Equal(t, uint16(2), lane.laneIndex)
assert.Equal(t, 2, lane.sockIdx)
assert.Equal(t, via.UdpAddr, lane.GetRemote())
assert.Equal(t, base, hostMap.Hosts[vpnAddrs[0]], "lane must not displace the base as primary")
base.lanes.Lock()
assert.Len(t, base.lanes.peerLanes, 1)
base.lanes.Unlock()
// A byte-identical stage-0 retransmit resends the cached response and
// must not register a second lane.
hm.completeLaneResponder(via, packet, []byte("resp"), respR, vpnAddrs)
base.lanes.Lock()
assert.Len(t, base.lanes.peerLanes, 1)
base.lanes.Unlock()
// An out-of-range lane index is refused.
respR2 := *respR
respR2.PeerLaneIndex = 9
respR2.LocalIndex = respR.LocalIndex + 1
hm.completeLaneResponder(via, packet, []byte("resp"), &respR2, vpnAddrs)
assert.Nil(t, hostMap.QueryIndex(respR2.LocalIndex))
}
func TestEnsureLanesBackoffOnStage0Failure(t *testing.T) {
hostMap := newHostMap(test.NewLogger())
_, ifce := newLaneTestConnectionManager(hostMap)
// laneCount enables multiport in the manager; the dummy CertState has no
// credential, so stage-0 construction must fail and release the slot with
// backoff rather than leaving it claimed forever.
hm := ifce.handshakeManager
hm.config.laneCount = 4
hm.config.lanePortCount = 4
hm.config.laneBasePort = 4242
vpnIp := netip.MustParseAddr("172.1.1.8")
base := newTestBaseHostInfo(vpnIp, 1100, 1200, 4)
for i := 1; i < 4; i++ {
base.lanes.noteLaneDemand(i)
}
hm.EnsureLanes(base)
base.lanes.Lock()
defer base.lanes.Unlock()
for i := 1; i < 4; i++ {
assert.False(t, base.lanes.txPending[i], "slot %d still pending", i)
assert.Equal(t, uint8(1), base.lanes.txFails[i], "slot %d fails", i)
assert.True(t, base.lanes.txRetryAt[i].After(time.Now()), "slot %d retryAt", i)
}
}
// Lanes are demand-driven: a base tunnel with no data-plane interest in a slot
// must not start a handshake for it, and one demand must not turn into an
// unbounded retry loop.
func TestEnsureLanesLazy(t *testing.T) {
hostMap := newHostMap(test.NewLogger())
_, ifce := newLaneTestConnectionManager(hostMap)
hm := ifce.handshakeManager
hm.config.laneCount = 4
hm.config.lanePortCount = 4
hm.config.laneBasePort = 4242
base := newTestBaseHostInfo(netip.MustParseAddr("172.1.1.9"), 1300, 1400, 4)
ls := base.lanes
// No demand: nothing is attempted, so nothing fails or backs off either.
hm.EnsureLanes(base)
ls.Lock()
for i := 1; i < 4; i++ {
assert.False(t, ls.txPending[i], "slot %d claimed without demand", i)
assert.Zero(t, ls.txFails[i], "slot %d attempted without demand", i)
assert.True(t, ls.txRetryAt[i].IsZero(), "slot %d backed off without demand", i)
}
ls.Unlock()
// Demand on one slot starts that slot alone. (Stage 0 fails on the test
// CertState, so the observable effect is a failure charged to slot 2.)
ls.noteLaneDemand(2)
hm.EnsureLanes(base)
ls.Lock()
assert.Equal(t, uint8(1), ls.txFails[2])
assert.Zero(t, ls.txFails[1], "untouched slot attempted")
assert.Zero(t, ls.txFails[3], "untouched slot attempted")
// The attempt consumed the demand, so a later tick past the backoff does
// not retry a lane nobody is asking for any more.
ls.txRetryAt[2] = time.Time{}
ls.Unlock()
hm.EnsureLanes(base)
ls.Lock()
assert.Equal(t, uint8(1), ls.txFails[2], "consumed demand was retried")
ls.Unlock()
// Slot 0 is the base tunnel and is never a lane.
ls.noteLaneDemand(0)
assert.False(t, ls.takeLaneDemand(0))
}
// A TX miss on an empty slot is what asks for the lane; a hit must not, and a
// relay-only peer never gets that far.
func TestSendInsideMessageRecordsLaneDemand(t *testing.T) {
hostMap := newHostMap(test.NewLogger())
_, ifce := newLaneTestConnectionManager(hostMap)
base := newTestBaseHostInfo(netip.MustParseAddr("172.1.1.10"), 1500, 1600, 4)
lane := newTestLaneHostInfo(base, 1, 1501, 1601, true)
init, _ := runTestHandshake(t)
cs, err := newConnectionStateFromResult(init)
require.NoError(t, err)
base.ConnectionState = cs
lane.ConnectionState = cs
writer := &recordingBatchWriter{}
newTx := func(laneSlot int) *txQueue {
sb := batch.NewSendBatch(writer, batch.SendBatchCap, 1<<16)
return &txQueue{laneSlot: laneSlot, base: sb, lane: sb}
}
pkt := tio.Packet{Bytes: []byte{0x45, 0, 0, 4, 1, 2, 3, 4}}
nb := make([]byte, 12)
// Empty slot: the send rides the base tunnel and flags demand for slot 1.
ifce.sendInsideMessage(base, pkt, nb, newTx(1))
assert.True(t, base.lanes.txDemand[1].Load(), "miss did not raise demand")
assert.False(t, base.lanes.txDemand[2].Load(), "demand raised on an unused slot")
// Established lane: the send uses it and asks for nothing.
base.lanes.txDemand[1].Store(false)
base.lanes.txLanes[1].Store(lane)
ifce.sendInsideMessage(base, pkt, nb, newTx(1))
assert.False(t, base.lanes.txDemand[1].Load(), "hit raised demand")
}