Skip to content

[meshnet] multiplex tcp streams for a node pair - #733

Merged
kraney merged 5 commits into
openconfig:mainfrom
kraney:meshnet-multiplex
Aug 12, 2026
Merged

[meshnet] multiplex tcp streams for a node pair#733
kraney merged 5 commits into
openconfig:mainfrom
kraney:meshnet-multiplex

Conversation

@kraney

@kraney kraney commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Note: this PR stacks on top of #732 , please merge that one first. The incremental changes start at sha ceb9511

  1. Created nodeStreamManager (stream_manager.go):
      • Keyed by nodeStreamKey{ topoNs string, peerIP string }.
      • Opens only one gRPC connection (grpc.Dial) and only one SendToStream 
         bidirectional streaming RPC per (peerIP, topoNs) pair.
      • Includes reference counting (GetOrCreateStream / ReleaseStream). When the last 
         wire for a topology targeting a peer node is removed, the shared stream and gRPC 
         connection close gracefully.
      • Features a high-capacity buffered channel (10,000 packet queue) and dedicated sender 
         worker loop with automatic reconnect logic.
  2. Updated TAP Reader Threads (grpcwire.go):
      • Modified RecvFrmLocalPodThread so TAP readers no longer execute individual grpc.Dial 
         or SendToStream calls.
      • Each TAP reader now acquires the shared topology stream via 
         nodeStream := streamMgr.GetOrCreateStream(wire.TopoNamespace, wire.PeerNodeIP) 
        and multiplexes packets into nodeStream.Send(payload).
  3. Per-Topology Isolation:
      • Topology topo-A and topology topo-B running between the same pair of physical 
          nodes maintain completely separate gRPC connections and streams.

Also:
The base design had a lot of single-interface RPCs
to update k8s and remote meshnets. For large numbers
of links it costs a lot of serial round trip wait time.
This batches things and pipelines them so that we avoid
a lot of unnecessary overhead delay at start time.

kraney added 2 commits August 11, 2026 15:31
  1. Created nodeStreamManager (stream_manager.go):
      • Keyed by nodeStreamKey{ topoNs string, peerIP string }.
      • Opens only one gRPC connection (grpc.Dial) and only one SendToStream bidirectional streaming RPC per (peerIP,
      topoNs) pair.
      • Includes reference counting (GetOrCreateStream / ReleaseStream). When the last wire for a topology targeting a
      peer node is removed, the shared stream and gRPC connection close gracefully.
      • Features a high-capacity buffered channel (10,000 packet queue) and dedicated sender worker loop with automatic
      reconnect logic.
  2. Updated TAP Reader Threads (grpcwire.go):
      • Modified RecvFrmLocalPodThread so TAP readers no longer execute individual grpc.Dial or SendToStream calls.
      • Each TAP reader now acquires the shared topology stream via nodeStream := streamMgr.GetOrCreateStream(wire.
      TopoNamespace, wire.PeerNodeIP) and multiplexes packets into nodeStream.Send(payload).
  3. Per-Topology Isolation:
      • Topology topo-A and topology topo-B running between the same pair of physical nodes maintain completely
      separate gRPC connections and streams.
The base design had a lot of single-interface RPCs
to update k8s and remote meshnets. For large numbers
of links it costs a lot of serial round trip wait time.
This batches things and pipelines them so that we avoid
a lot of unnecessary overhead delay at start time.
@kraney
kraney force-pushed the meshnet-multiplex branch from 61df5fe to c86997a Compare August 11, 2026 15:32
Comment thread third_party/meshnet/utils/wireutil/sys_tune.go Outdated
Comment thread third_party/meshnet/daemon/grpcwire/gwire_recon.go
Comment thread third_party/meshnet/daemon/grpcwire/gwire_recon.go Outdated
Comment thread third_party/meshnet/daemon/grpcwire/stream_manager.go Outdated
  ### 1. Unused getEnvString in sys_tune.go

  • Removed the unused private helper sys_tune.go.
  ──────
  ### 2. Remove gwire_recon.go

  • Removed the obsolete single-wire gwire_recon.go function, as all updates are now handled by gwire_recon.go.
  • Updated the gwire_recon.go doc comment.
  ──────
  ### 3. Namespace Isolation in Status Batch Updates

  • Problem: gwire_recon.go used updates[0].wire.TopoNamespace for the entire batch, which caused cross-namespace collisions when multiple
  topologies ran on the same cluster.
  • Fix: Updated gwire_recon.go to group updates by statusGroupKey{nodeName, topoNs} and delegate each group to gwire_recon.go.
  • Added unit test gwire_recon_test.go in gwire_recon_test.go to verify concurrent multi-namespace status updates.
  ──────
  ### 4. Unused s.conn and s.mu in stream_manager.go

  • s.conn was assigned in run() but never read elsewhere because the active connection lifecycle is managed directly by drainAndSend(ctx,
  cancel, conn, stream) (which closes conn in its defer block upon termination).
  • Removed the unused conn and mu fields from stream_manager.go along with the redundant mutex locking.
  ──────
  ### 5. Additional Dead Code Cleanup

  • **grpcwire.go**:
      • Removed unused (w *wireMap) Delete method.
      • Simplified grpcwire.go to delegate to grpcwire.go, eliminating duplicated locking and update logic.
  • **gwire_map.go**:
      • Removed unused (w *wireMap) DeleteWoLock method.
  • **handler.go**:
      • Removed commented-out legacy call in handler.go.
  #### 1. Status Queue Deadlocks & Ghost Wires

  • gwire_recon.go:
      • Replaced the scalar flushSignal chan struct{} in gwire_recon.go with a slice (flushSignals []chan struct{}) so all flush requests
      in a batch are tracked and closed without overwrites.
      • Added wire.mu.Lock() / wire.mu.Unlock() to gwire_recon.go to eliminate data races against concurrent peer updates.
      • Added gwire_recon.go to gwire_recon.go before calling deleteGRPCWireStatus.
      • Added in-memory presence checks via grpcwire.go in gwire_recon.go and gwire_recon.go to prevent writing deleted wires back into
      K8s.
      • Handled apierrors.IsNotFound(err) gracefully in gwire_recon.go.

  #### 2. Leaked TAP File Descriptors on Wire Deletion

  • gwire_map.go & grpcwire.go:
      • Added wires.CloseAndRemoveHandle(key) to close the underlying *os.File and delete it from wires.handles.
      • Updated gwire_map.go to avoid deleting the handle prematurely before grpcwire.go closes it.
      • Updated grpcwire.go and gwire_map.go to close and remove TAP file descriptors.

  #### 3. Goroutine & Buffer Leak on Teardown

  • grpcwire.go:
      • Made packet channel writes in grpcwire.go select on <-wire.StopC. If teardown occurs, the reader immediately returns the buffer
      to packetPool and exits cleanly.

  #### 4. Controller Batch Error Handling & Bounds Checking

  • controller.go:
      • Added nil checks for batchResp and slice bounds validation against chunkLinks.
      • Captured individual remote link creation failures and returned an aggregate errors.Join(batchErrs...) from controller.go so the
      reconciler retries when remote wire setup fails.

  #### 5. Duplicate Packet Receive Goroutines & Stream Refcount Leaks

  • gwire_rpc_handlers.go & handler.go:
      • Updated gwire_rpc_handlers.go to return (wire *GRPCWire, created bool, err error).
      • Updated handler.go and handler.go to only spawn grpcwire.go when created == true.

  #### 6. Throttled Reconnection on Send Errors

  • stream_manager.go:
      • Added a backoff delay in stream_manager.go after stream_manager.go returns due to connection drops, preventing 100% CPU spin
      loops while continuing to respect stopChan.
@kraney
kraney merged commit 20bd60e into openconfig:main Aug 12, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants