From 562ccfdbc14912815482bde68470ab67a1da243c Mon Sep 17 00:00:00 2001 From: Mikhail Date: Sat, 16 May 2026 10:36:14 +0300 Subject: [PATCH] Cover QUIC carrier in mesh live smoke --- .../cmd/mesh-live-smoke/main.go | 82 +++++++++++++++++++ .../DISTRIBUTED_FABRIC_NODE_PROTOCOL_PLAN.md | 2 + 2 files changed, 84 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 1bec065..551314c 100644 --- a/agents/rap-node-agent/cmd/mesh-live-smoke/main.go +++ b/agents/rap-node-agent/cmd/mesh-live-smoke/main.go @@ -2,8 +2,14 @@ package main import ( "context" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" "encoding/json" "fmt" + "math/big" "net/http" "net/http/httptest" "os" @@ -35,6 +41,8 @@ type smokeReport struct { FabricSessionAccepted bool `json:"fabric_session_accepted"` FabricSessionRoundTrips int `json:"fabric_session_round_trips"` FabricVPNPacketAccepted bool `json:"fabric_vpn_packet_accepted"` + FabricQUICAccepted bool `json:"fabric_quic_accepted"` + FabricQUICEndpoint string `json:"fabric_quic_endpoint"` FabricSessionLatencyMS int64 `json:"fabric_session_latency_ms"` FabricSessionEndpoint string `json:"fabric_session_endpoint"` PeerEndpoints map[string]any `json:"peer_endpoints"` @@ -135,6 +143,10 @@ func run(ctx context.Context) (smokeReport, error) { if err != nil { return smokeReport{}, fmt.Errorf("fabric vpn packet session smoke: %w", err) } + fabricQUICAccepted, fabricQUICEndpoint, err := smokeQUICFabricSession(ctx) + if err != nil { + return smokeReport{}, fmt.Errorf("fabric quic smoke: %w", err) + } return smokeReport{ Stage: "C17F scoped synthetic config plus live HTTP transport", @@ -149,6 +161,8 @@ func run(ctx context.Context) (smokeReport, error) { FabricSessionAccepted: fabricSessionAccepted, FabricSessionRoundTrips: 2, FabricVPNPacketAccepted: fabricVPNPacketAccepted, + FabricQUICAccepted: fabricQUICAccepted, + FabricQUICEndpoint: fabricQUICEndpoint, FabricSessionLatencyMS: fabricSessionLatency.Milliseconds(), FabricSessionEndpoint: nodeB.URL + "/mesh/v1/fabric/session/ws", PeerEndpoints: map[string]any{ @@ -159,6 +173,74 @@ func run(ctx context.Context) (smokeReport, error) { }, nil } +func smokeQUICFabricSession(ctx context.Context) (bool, string, error) { + server, err := mesh.StartQUICFabricServer(ctx, mesh.QUICFabricServerConfig{ + ListenAddr: "127.0.0.1:0", + TLSConfig: smokeQUICTLSConfig(), + }) + if err != nil { + return false, "", err + } + defer server.Close() + endpoint := server.Addr().String() + session, err := mesh.NewQUICFabricTransport(nil).Connect(ctx, mesh.FabricTransportTarget{ + Endpoint: endpoint, + TLSConfig: &tls.Config{ + InsecureSkipVerify: true, + NextProtos: []string{"rap-fabric-data-session-v1"}, + }, + Timeout: 3 * time.Second, + InboundBuffer: 4, + ErrorBuffer: 4, + }) + if err != nil { + return false, endpoint, err + } + defer session.Close() + if err := session.Send(ctx, fabricproto.Frame{ + Type: fabricproto.FramePing, + Sequence: uint64(time.Now().UnixNano()), + Payload: []byte("mesh-live-smoke-quic"), + }); err != nil { + return false, endpoint, err + } + timer := time.NewTimer(3 * time.Second) + defer timer.Stop() + for { + select { + case frame := <-session.Frames(): + return frame.Type == fabricproto.FramePong && string(frame.Payload) == "mesh-live-smoke-quic", endpoint, nil + case err := <-session.Errors(): + return false, endpoint, err + case <-timer.C: + return false, endpoint, fmt.Errorf("timed out waiting for quic pong") + case <-ctx.Done(): + return false, endpoint, ctx.Err() + } + } +} + +func smokeQUICTLSConfig() *tls.Config { + key, _ := rsa.GenerateKey(rand.Reader, 2048) + template := x509.Certificate{ + SerialNumber: big.NewInt(time.Now().UnixNano()), + Subject: pkix.Name{CommonName: "mesh-live-smoke"}, + NotBefore: time.Now().Add(-time.Minute), + NotAfter: time.Now().Add(time.Hour), + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + DNSNames: []string{"localhost"}, + } + certDER, _ := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key) + return &tls.Config{ + Certificates: []tls.Certificate{{ + Certificate: [][]byte{certDER}, + PrivateKey: key, + }}, + NextProtos: []string{"rap-fabric-data-session-v1"}, + } +} + func smokeFabricVPNPacketOverSession(ctx context.Context, fabricSession *mesh.FabricSessionClient) (bool, error) { const streamID uint64 = 4400 pump := fabricSession.StartPump(ctx, mesh.FabricSessionPumpOptions{ diff --git a/docs/architecture/DISTRIBUTED_FABRIC_NODE_PROTOCOL_PLAN.md b/docs/architecture/DISTRIBUTED_FABRIC_NODE_PROTOCOL_PLAN.md index 8577b18..38f23ba 100644 --- a/docs/architecture/DISTRIBUTED_FABRIC_NODE_PROTOCOL_PLAN.md +++ b/docs/architecture/DISTRIBUTED_FABRIC_NODE_PROTOCOL_PLAN.md @@ -305,6 +305,8 @@ Node-agent can now gate the QUIC listener with `RAP_MESH_QUIC_FABRIC_ENABLED` / `RAP_MESH_QUIC_FABRIC_LISTEN_ADDR`, report it in heartbeat metadata, and pass the setting through host-agent install/update profiles. +`mesh-live-smoke` verifies the QUIC carrier by starting a temporary QUIC fabric +server and requiring a `PING`/`PONG` round trip over `QUICFabricTransport`. Deliverables: