From 0a15617c521ea0f574cd035dc9edd4d170f518b2 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Sat, 16 May 2026 00:49:18 +0300 Subject: [PATCH] Cover VPN packets in fabric session smoke --- .../cmd/mesh-live-smoke/main.go | 50 +++++++++++++++++++ .../DISTRIBUTED_FABRIC_NODE_PROTOCOL_PLAN.md | 3 ++ 2 files changed, 53 insertions(+) diff --git a/agents/rap-node-agent/cmd/mesh-live-smoke/main.go b/agents/rap-node-agent/cmd/mesh-live-smoke/main.go index 2b8ca32..1bec065 100644 --- a/agents/rap-node-agent/cmd/mesh-live-smoke/main.go +++ b/agents/rap-node-agent/cmd/mesh-live-smoke/main.go @@ -12,6 +12,7 @@ import ( "github.com/example/remote-access-platform/agents/rap-node-agent/internal/fabricproto" "github.com/example/remote-access-platform/agents/rap-node-agent/internal/mesh" + "github.com/example/remote-access-platform/agents/rap-node-agent/internal/vpnruntime" ) type smokeNode struct { @@ -33,6 +34,7 @@ type smokeReport struct { TestServiceEchoPayload string `json:"test_service_echo_payload"` FabricSessionAccepted bool `json:"fabric_session_accepted"` FabricSessionRoundTrips int `json:"fabric_session_round_trips"` + FabricVPNPacketAccepted bool `json:"fabric_vpn_packet_accepted"` FabricSessionLatencyMS int64 `json:"fabric_session_latency_ms"` FabricSessionEndpoint string `json:"fabric_session_endpoint"` PeerEndpoints map[string]any `json:"peer_endpoints"` @@ -129,6 +131,10 @@ func run(ctx context.Context) (smokeReport, error) { string(firstFabricSessionResponse.Payload) == "mesh-live-smoke-fabric-session" && secondFabricSessionResponse.Type == fabricproto.FramePong && string(secondFabricSessionResponse.Payload) == "mesh-live-smoke-fabric-session-2" + fabricVPNPacketAccepted, err := smokeFabricVPNPacketOverSession(ctx, fabricSession) + if err != nil { + return smokeReport{}, fmt.Errorf("fabric vpn packet session smoke: %w", err) + } return smokeReport{ Stage: "C17F scoped synthetic config plus live HTTP transport", @@ -142,6 +148,7 @@ func run(ctx context.Context) (smokeReport, error) { TestServiceEchoPayload: testService.Response.EchoPayload, FabricSessionAccepted: fabricSessionAccepted, FabricSessionRoundTrips: 2, + FabricVPNPacketAccepted: fabricVPNPacketAccepted, FabricSessionLatencyMS: fabricSessionLatency.Milliseconds(), FabricSessionEndpoint: nodeB.URL + "/mesh/v1/fabric/session/ws", PeerEndpoints: map[string]any{ @@ -152,6 +159,49 @@ func run(ctx context.Context) (smokeReport, error) { }, nil } +func smokeFabricVPNPacketOverSession(ctx context.Context, fabricSession *mesh.FabricSessionClient) (bool, error) { + const streamID uint64 = 4400 + pump := fabricSession.StartPump(ctx, mesh.FabricSessionPumpOptions{ + OutboundBuffer: 4, + InboundBuffer: 4, + ErrorBuffer: 4, + }) + defer pump.Close() + if err := pump.Send(ctx, fabricproto.Frame{ + Type: fabricproto.FrameOpenStream, + StreamID: streamID, + TrafficClass: fabricproto.TrafficClassInteractive, + }); err != nil { + return false, err + } + transport := &vpnruntime.FabricSessionPacketTransport{ + Sender: pump, + StreamID: streamID, + VPNConnectionID: "vpn-smoke", + SendDirection: vpnruntime.FabricDirectionGatewayToClient, + TrafficClass: vpnruntime.FabricTrafficClassInteractive, + } + if err := transport.SendGatewayPacketBatch(ctx, [][]byte{[]byte("fabric-vpn-packet-smoke")}); err != nil { + return false, err + } + timer := time.NewTimer(3 * time.Second) + defer timer.Stop() + for { + select { + case frame := <-pump.Frames(): + if frame.Type == fabricproto.FrameAck && frame.StreamID == streamID && frame.Sequence == 1 { + return true, nil + } + case err := <-pump.Errors(): + return false, err + case <-timer.C: + return false, fmt.Errorf("timed out waiting for fabric vpn packet ack") + case <-ctx.Done(): + return false, ctx.Err() + } + } +} + func writeSmokeScopedConfig(local mesh.PeerIdentity, peers map[string]string, routes []mesh.SyntheticRoute) (string, error) { path := filepath.Join(os.TempDir(), "rap-c17e-node-a-scoped-mesh.json") payload, err := json.Marshal(mesh.ScopedSyntheticConfig{ diff --git a/docs/architecture/DISTRIBUTED_FABRIC_NODE_PROTOCOL_PLAN.md b/docs/architecture/DISTRIBUTED_FABRIC_NODE_PROTOCOL_PLAN.md index 351922f..31e1a4e 100644 --- a/docs/architecture/DISTRIBUTED_FABRIC_NODE_PROTOCOL_PLAN.md +++ b/docs/architecture/DISTRIBUTED_FABRIC_NODE_PROTOCOL_PLAN.md @@ -275,6 +275,9 @@ packet delivery can move away from JSON production envelopes in a gated mode. `FabricSessionPacketTransport` now adapts that mapper to the existing `PacketTransport` interface and can demultiplex inbound DATA frames into the VPN packet inbox by stream id. +`mesh-live-smoke` now sends a real VPN packet batch through +`FabricSessionPacketTransport` over the WebSocket fabric session and requires a +stream ACK from the remote node. Deliverables: