From 6dc7a61c9da42fc35ff8d1e66d7c3fecb496a524 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Sat, 16 May 2026 11:51:32 +0300 Subject: [PATCH] Configure QUIC fabric idle TTL --- agents/rap-node-agent/cmd/rap-node-agent/main.go | 7 +++++++ agents/rap-node-agent/internal/config/config.go | 5 +++++ agents/rap-node-agent/internal/config/config_test.go | 4 ++++ agents/rap-node-agent/internal/hostagent/config.go | 4 ++++ agents/rap-node-agent/internal/hostagent/docker.go | 1 + agents/rap-node-agent/internal/hostagent/linux.go | 1 + agents/rap-node-agent/internal/hostagent/profile.go | 4 ++++ agents/rap-node-agent/internal/hostagent/update.go | 1 + agents/rap-node-agent/internal/hostagent/windows.go | 1 + docs/architecture/DISTRIBUTED_FABRIC_NODE_PROTOCOL_PLAN.md | 3 +++ 10 files changed, 31 insertions(+) diff --git a/agents/rap-node-agent/cmd/rap-node-agent/main.go b/agents/rap-node-agent/cmd/rap-node-agent/main.go index 088fa11..fafd006 100644 --- a/agents/rap-node-agent/cmd/rap-node-agent/main.go +++ b/agents/rap-node-agent/cmd/rap-node-agent/main.go @@ -1161,6 +1161,9 @@ func newVPNFabricQUICTransport(cfg config.Config) *mesh.QUICFabricTransport { if cfg.VPNFabricQUICMaxStreamsPerConn > 0 { transport.MaxStreamsPerConn = cfg.VPNFabricQUICMaxStreamsPerConn } + if cfg.VPNFabricQUICIdleTTL > 0 { + transport.IdleTTL = cfg.VPNFabricQUICIdleTTL + } return transport } @@ -2017,6 +2020,9 @@ func applyRefreshedSyntheticMeshConfig(ctx context.Context, cfg config.Config, i } else if cfg.VPNFabricQUICMaxStreamsPerConn > 0 { meshState.VPNFabricQUICTransport.MaxStreamsPerConn = cfg.VPNFabricQUICMaxStreamsPerConn } + if meshState.VPNFabricQUICTransport != nil && cfg.VPNFabricQUICIdleTTL > 0 { + meshState.VPNFabricQUICTransport.IdleTTL = cfg.VPNFabricQUICIdleTTL + } if meshState.VPNFabricSessionDialStats == nil { meshState.VPNFabricSessionDialStats = newVPNFabricSessionDialStats() } @@ -2983,6 +2989,7 @@ func heartbeatPayload(cfg config.Config, identity state.Identity, meshState *syn quicSnapshot := meshState.VPNFabricQUICTransport.Snapshot() report["quic_sessions"] = quicSnapshot report["quic_max_streams_per_conn"] = meshState.VPNFabricQUICTransport.MaxStreamsPerConn + report["quic_idle_ttl_seconds"] = int(meshState.VPNFabricQUICTransport.IdleTTL.Seconds()) } if meshState != nil && meshState.VPNFabricSessionDialStats != nil { report["dial_stats"] = meshState.VPNFabricSessionDialStats.Report(observedAt) diff --git a/agents/rap-node-agent/internal/config/config.go b/agents/rap-node-agent/internal/config/config.go index 08ce438..49377a9 100644 --- a/agents/rap-node-agent/internal/config/config.go +++ b/agents/rap-node-agent/internal/config/config.go @@ -31,6 +31,7 @@ type Config struct { MeshQUICFabricEnabled bool MeshQUICFabricListenAddr string VPNFabricQUICMaxStreamsPerConn int + VPNFabricQUICIdleTTL time.Duration MeshProductionObservationSinkCapacity int MeshListenAddr string MeshListenPortMode string @@ -73,6 +74,7 @@ func Load(args []string, env map[string]string) (Config, error) { fs.BoolVar(&cfg.MeshQUICFabricEnabled, "mesh-quic-fabric-enabled", getEnvBool(env, "RAP_MESH_QUIC_FABRIC_ENABLED", false), "Enable QUIC/UDP fabric listener. Disabled by default.") fs.StringVar(&cfg.MeshQUICFabricListenAddr, "mesh-quic-fabric-listen-addr", getEnv(env, "RAP_MESH_QUIC_FABRIC_LISTEN_ADDR", ""), "Listen address for QUIC/UDP fabric endpoint, for example :19443.") fs.IntVar(&cfg.VPNFabricQUICMaxStreamsPerConn, "vpn-fabric-quic-max-streams-per-conn", getEnvInt(env, "RAP_VPN_FABRIC_QUIC_MAX_STREAMS_PER_CONN", 64), "Maximum logical fabric-session streams per cached VPN QUIC carrier connection.") + fs.DurationVar(&cfg.VPNFabricQUICIdleTTL, "vpn-fabric-quic-idle-ttl", time.Duration(getEnvInt(env, "RAP_VPN_FABRIC_QUIC_IDLE_TTL_SECONDS", 300))*time.Second, "Idle TTL for cached VPN QUIC carrier connections.") fs.IntVar(&cfg.MeshProductionObservationSinkCapacity, "mesh-production-observation-sink-capacity", getEnvSignedInt(env, "RAP_MESH_PRODUCTION_OBSERVATION_SINK_CAPACITY", 0), "Bounded local metadata-only production envelope observation sink capacity. Disabled when 0.") fs.StringVar(&cfg.MeshListenAddr, "mesh-listen-addr", getEnv(env, "RAP_MESH_LISTEN_ADDR", ""), "Listen address for disabled-by-default C17E synthetic mesh HTTP endpoint.") fs.StringVar(&cfg.MeshListenPortMode, "mesh-listen-port-mode", getEnv(env, "RAP_MESH_LISTEN_PORT_MODE", "manual"), "Mesh listen port behavior: manual, auto, or disabled.") @@ -113,6 +115,9 @@ func Load(args []string, env map[string]string) (Config, error) { if cfg.VPNFabricQUICMaxStreamsPerConn <= 0 { cfg.VPNFabricQUICMaxStreamsPerConn = 64 } + if cfg.VPNFabricQUICIdleTTL <= 0 { + cfg.VPNFabricQUICIdleTTL = 5 * time.Minute + } cfg.MeshAdvertiseEndpoint = strings.TrimRight(strings.TrimSpace(cfg.MeshAdvertiseEndpoint), "/") cfg.MeshAdvertiseEndpointsJSON = strings.TrimSpace(cfg.MeshAdvertiseEndpointsJSON) cfg.MeshAdvertiseTransport = strings.TrimSpace(cfg.MeshAdvertiseTransport) diff --git a/agents/rap-node-agent/internal/config/config_test.go b/agents/rap-node-agent/internal/config/config_test.go index 34042f6..95521f5 100644 --- a/agents/rap-node-agent/internal/config/config_test.go +++ b/agents/rap-node-agent/internal/config/config_test.go @@ -25,6 +25,7 @@ func TestLoadConfigFromEnvAndArgs(t *testing.T) { "RAP_MESH_QUIC_FABRIC_ENABLED": "true", "RAP_MESH_QUIC_FABRIC_LISTEN_ADDR": ":19443", "RAP_VPN_FABRIC_QUIC_MAX_STREAMS_PER_CONN": "24", + "RAP_VPN_FABRIC_QUIC_IDLE_TTL_SECONDS": "120", "RAP_MESH_PRODUCTION_OBSERVATION_SINK_CAPACITY": "5", "RAP_MESH_LISTEN_ADDR": "127.0.0.1:19001", "RAP_MESH_LISTEN_PORT_MODE": "auto", @@ -83,6 +84,9 @@ func TestLoadConfigFromEnvAndArgs(t *testing.T) { if cfg.VPNFabricQUICMaxStreamsPerConn != 24 { t.Fatalf("VPNFabricQUICMaxStreamsPerConn = %d, want 24", cfg.VPNFabricQUICMaxStreamsPerConn) } + if cfg.VPNFabricQUICIdleTTL != 120*time.Second { + t.Fatalf("VPNFabricQUICIdleTTL = %s, want 120s", cfg.VPNFabricQUICIdleTTL) + } if cfg.MeshProductionObservationSinkCapacity != 5 { t.Fatalf("MeshProductionObservationSinkCapacity = %d, want 5", cfg.MeshProductionObservationSinkCapacity) } diff --git a/agents/rap-node-agent/internal/hostagent/config.go b/agents/rap-node-agent/internal/hostagent/config.go index ea28b15..864f349 100644 --- a/agents/rap-node-agent/internal/hostagent/config.go +++ b/agents/rap-node-agent/internal/hostagent/config.go @@ -34,6 +34,7 @@ type RuntimeConfig struct { MeshQUICFabricEnabled bool MeshQUICFabricListenAddr string VPNFabricQUICMaxStreamsPerConn int + VPNFabricQUICIdleTTLSeconds int MeshListenAddr string MeshListenPortMode string MeshListenAutoPortStart int @@ -70,6 +71,9 @@ func (cfg RuntimeConfig) Normalize() RuntimeConfig { if cfg.VPNFabricQUICMaxStreamsPerConn <= 0 { cfg.VPNFabricQUICMaxStreamsPerConn = 64 } + if cfg.VPNFabricQUICIdleTTLSeconds <= 0 { + cfg.VPNFabricQUICIdleTTLSeconds = 300 + } cfg.MeshListenPortMode = strings.ToLower(strings.TrimSpace(cfg.MeshListenPortMode)) cfg.MeshAdvertiseEndpoint = strings.TrimRight(strings.TrimSpace(cfg.MeshAdvertiseEndpoint), "/") cfg.MeshAdvertiseEndpointsJSON = strings.TrimSpace(cfg.MeshAdvertiseEndpointsJSON) diff --git a/agents/rap-node-agent/internal/hostagent/docker.go b/agents/rap-node-agent/internal/hostagent/docker.go index 80fcb14..be0178b 100644 --- a/agents/rap-node-agent/internal/hostagent/docker.go +++ b/agents/rap-node-agent/internal/hostagent/docker.go @@ -268,6 +268,7 @@ func NodeAgentEnvWithStateDir(cfg RuntimeConfig, stateDir string) []string { "RAP_VPN_FABRIC_SESSION_TRANSPORT_ENABLED=" + boolString(cfg.VPNFabricSessionTransportEnabled), "RAP_MESH_QUIC_FABRIC_ENABLED=" + boolString(cfg.MeshQUICFabricEnabled), "RAP_VPN_FABRIC_QUIC_MAX_STREAMS_PER_CONN=" + strconv.Itoa(cfg.VPNFabricQUICMaxStreamsPerConn), + "RAP_VPN_FABRIC_QUIC_IDLE_TTL_SECONDS=" + strconv.Itoa(cfg.VPNFabricQUICIdleTTLSeconds), } if cfg.JoinToken != "" { env = append(env, "RAP_JOIN_TOKEN="+cfg.JoinToken) diff --git a/agents/rap-node-agent/internal/hostagent/linux.go b/agents/rap-node-agent/internal/hostagent/linux.go index 7b5711e..6eeead1 100644 --- a/agents/rap-node-agent/internal/hostagent/linux.go +++ b/agents/rap-node-agent/internal/hostagent/linux.go @@ -77,6 +77,7 @@ func LinuxInstallConfigFromProfile(profile LinuxInstallProfile) LinuxInstallConf MeshQUICFabricEnabled: profile.MeshQUICFabricEnabled, MeshQUICFabricListenAddr: profile.MeshQUICFabricListenAddr, VPNFabricQUICMaxStreamsPerConn: profile.VPNFabricQUICMaxStreamsPerConn, + VPNFabricQUICIdleTTLSeconds: profile.VPNFabricQUICIdleTTLSeconds, MeshListenAddr: profile.MeshListenAddr, MeshListenPortMode: profile.MeshListenPortMode, MeshListenAutoPortStart: profile.MeshListenAutoPortStart, diff --git a/agents/rap-node-agent/internal/hostagent/profile.go b/agents/rap-node-agent/internal/hostagent/profile.go index c87ed07..0091fd4 100644 --- a/agents/rap-node-agent/internal/hostagent/profile.go +++ b/agents/rap-node-agent/internal/hostagent/profile.go @@ -35,6 +35,7 @@ type DockerInstallProfile struct { MeshQUICFabricEnabled bool `json:"mesh_quic_fabric_enabled"` MeshQUICFabricListenAddr string `json:"mesh_quic_fabric_listen_addr"` VPNFabricQUICMaxStreamsPerConn int `json:"vpn_fabric_quic_max_streams_per_conn"` + VPNFabricQUICIdleTTLSeconds int `json:"vpn_fabric_quic_idle_ttl_seconds"` MeshListenAddr string `json:"mesh_listen_addr"` MeshListenPortMode string `json:"mesh_listen_port_mode"` MeshListenAutoPortStart int `json:"mesh_listen_auto_port_start"` @@ -82,6 +83,7 @@ type WindowsInstallProfile struct { MeshQUICFabricEnabled bool `json:"mesh_quic_fabric_enabled"` MeshQUICFabricListenAddr string `json:"mesh_quic_fabric_listen_addr"` VPNFabricQUICMaxStreamsPerConn int `json:"vpn_fabric_quic_max_streams_per_conn"` + VPNFabricQUICIdleTTLSeconds int `json:"vpn_fabric_quic_idle_ttl_seconds"` MeshListenAddr string `json:"mesh_listen_addr"` MeshListenPortMode string `json:"mesh_listen_port_mode"` MeshListenAutoPortStart int `json:"mesh_listen_auto_port_start"` @@ -119,6 +121,7 @@ type LinuxInstallProfile struct { MeshQUICFabricEnabled bool `json:"mesh_quic_fabric_enabled"` MeshQUICFabricListenAddr string `json:"mesh_quic_fabric_listen_addr"` VPNFabricQUICMaxStreamsPerConn int `json:"vpn_fabric_quic_max_streams_per_conn"` + VPNFabricQUICIdleTTLSeconds int `json:"vpn_fabric_quic_idle_ttl_seconds"` MeshListenAddr string `json:"mesh_listen_addr"` MeshListenPortMode string `json:"mesh_listen_port_mode"` MeshListenAutoPortStart int `json:"mesh_listen_auto_port_start"` @@ -301,6 +304,7 @@ func RuntimeConfigFromProfile(profile DockerInstallProfile) RuntimeConfig { MeshQUICFabricEnabled: profile.MeshQUICFabricEnabled, MeshQUICFabricListenAddr: profile.MeshQUICFabricListenAddr, VPNFabricQUICMaxStreamsPerConn: profile.VPNFabricQUICMaxStreamsPerConn, + VPNFabricQUICIdleTTLSeconds: profile.VPNFabricQUICIdleTTLSeconds, MeshListenAddr: profile.MeshListenAddr, MeshListenPortMode: profile.MeshListenPortMode, MeshListenAutoPortStart: profile.MeshListenAutoPortStart, diff --git a/agents/rap-node-agent/internal/hostagent/update.go b/agents/rap-node-agent/internal/hostagent/update.go index fc3278d..249e4fb 100644 --- a/agents/rap-node-agent/internal/hostagent/update.go +++ b/agents/rap-node-agent/internal/hostagent/update.go @@ -599,6 +599,7 @@ func (m DockerManager) runtimeConfigFromContainer(ctx context.Context, runner Co MeshQUICFabricEnabled: parseBool(env["RAP_MESH_QUIC_FABRIC_ENABLED"]), MeshQUICFabricListenAddr: env["RAP_MESH_QUIC_FABRIC_LISTEN_ADDR"], VPNFabricQUICMaxStreamsPerConn: parseInt(env["RAP_VPN_FABRIC_QUIC_MAX_STREAMS_PER_CONN"]), + VPNFabricQUICIdleTTLSeconds: parseInt(env["RAP_VPN_FABRIC_QUIC_IDLE_TTL_SECONDS"]), MeshListenAddr: env["RAP_MESH_LISTEN_ADDR"], MeshListenPortMode: env["RAP_MESH_LISTEN_PORT_MODE"], MeshListenAutoPortStart: parseInt(env["RAP_MESH_LISTEN_AUTO_PORT_START"]), diff --git a/agents/rap-node-agent/internal/hostagent/windows.go b/agents/rap-node-agent/internal/hostagent/windows.go index 2d66e68..89dc212 100644 --- a/agents/rap-node-agent/internal/hostagent/windows.go +++ b/agents/rap-node-agent/internal/hostagent/windows.go @@ -71,6 +71,7 @@ func WindowsInstallConfigFromProfile(profile WindowsInstallProfile) WindowsInsta MeshQUICFabricEnabled: profile.MeshQUICFabricEnabled, MeshQUICFabricListenAddr: profile.MeshQUICFabricListenAddr, VPNFabricQUICMaxStreamsPerConn: profile.VPNFabricQUICMaxStreamsPerConn, + VPNFabricQUICIdleTTLSeconds: profile.VPNFabricQUICIdleTTLSeconds, MeshListenAddr: profile.MeshListenAddr, MeshListenPortMode: profile.MeshListenPortMode, MeshListenAutoPortStart: profile.MeshListenAutoPortStart, diff --git a/docs/architecture/DISTRIBUTED_FABRIC_NODE_PROTOCOL_PLAN.md b/docs/architecture/DISTRIBUTED_FABRIC_NODE_PROTOCOL_PLAN.md index c90a201..3824733 100644 --- a/docs/architecture/DISTRIBUTED_FABRIC_NODE_PROTOCOL_PLAN.md +++ b/docs/architecture/DISTRIBUTED_FABRIC_NODE_PROTOCOL_PLAN.md @@ -371,6 +371,9 @@ profiles. QUIC stream-limit rejects are classified as capacity pressure instead of peer endpoint failure, so local health feedback does not incorrectly demote a healthy but saturated carrier. +Cached QUIC carrier idle TTL is configurable through +`RAP_VPN_FABRIC_QUIC_IDLE_TTL_SECONDS` / `-vpn-fabric-quic-idle-ttl` and +propagated by host-agent install profiles. Deliverables: