Files
rdp-proxy/agents/rap-node-agent/internal/mesh/fabric_quic_transport.go
T

997 lines
29 KiB
Go

package mesh
import (
"context"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/hex"
"encoding/json"
"fmt"
"net"
"sort"
"strings"
"sync"
"time"
"github.com/example/remote-access-platform/agents/rap-node-agent/internal/fabricproto"
"github.com/quic-go/quic-go"
)
const fabricQUICNextProto = "rap-fabric-data-session-v1"
const fabricQUICReverseHelloPrefix = "rap-fabric-reverse-hello-v1:"
const defaultQUICFabricConnIdleTTL = 5 * time.Minute
const defaultQUICFabricMaxStreamsPerConn = 64
const ErrQUICFabricStreamLimitReached = quicFabricError("quic fabric stream limit reached")
type quicFabricError string
func (e quicFabricError) Error() string {
return string(e)
}
type QUICFabricTransport struct {
Config *quic.Config
LocalPeerID string
IdleTTL time.Duration
MaxStreamsPerConn int
DialAddr func(context.Context, string, *tls.Config, *quic.Config) (*quic.Conn, error)
mu sync.Mutex
conns map[string]*quicFabricConnEntry
reverseConns map[string]*quicFabricConnEntry
inboundProductionHandler func(context.Context, ProductionEnvelope) (ProductionForwardResult, error)
inboundWebIngressHandler func(context.Context, []byte) ([]byte, error)
inboundFabricControlHandler func(context.Context, []byte) ([]byte, error)
inboundSyntheticHandler func(context.Context, SyntheticEnvelope) (SyntheticEnvelope, error)
logger FabricSessionEventLogger
stats QUICFabricTransportStats
}
type QUICFabricTransportStats struct {
Opens uint64 `json:"opens"`
Reuses uint64 `json:"reuses"`
ReverseHelloSent uint64 `json:"reverse_hello_sent"`
ReverseHelloFailed uint64 `json:"reverse_hello_failed"`
ReverseRegisters uint64 `json:"reverse_registers"`
ReverseReuses uint64 `json:"reverse_reuses"`
OpenFailures uint64 `json:"open_failures"`
ClosedEvicted uint64 `json:"closed_evicted"`
CloseAllCalls uint64 `json:"close_all_calls"`
IdleEvicted uint64 `json:"idle_evicted"`
StreamOpens uint64 `json:"stream_opens"`
StreamCloses uint64 `json:"stream_closes"`
StreamLimitRejects uint64 `json:"stream_limit_rejects"`
}
type QUICFabricTransportSnapshot struct {
SchemaVersion string `json:"schema_version"`
LocalPeerID string `json:"local_peer_id,omitempty"`
ActiveCount int `json:"active_count"`
ActiveStreams int `json:"active_streams"`
MaxStreamsPerConn int `json:"max_streams_per_conn"`
SaturatedConnections int `json:"saturated_connections"`
CapacityPressurePercent int `json:"capacity_pressure_percent"`
Connections []QUICFabricConnSnapshot `json:"connections,omitempty"`
Stats QUICFabricTransportStats `json:"stats"`
}
type QUICFabricConnSnapshot struct {
PeerID string `json:"peer_id,omitempty"`
Endpoint string `json:"endpoint,omitempty"`
CertSHA256 string `json:"cert_sha256,omitempty"`
Direction string `json:"direction,omitempty"`
ActiveStreams int `json:"active_streams"`
MaxStreams int `json:"max_streams"`
CapacityPressurePercent int `json:"capacity_pressure_percent"`
Saturated bool `json:"saturated"`
LastUsedUnixSec int64 `json:"last_used_unix_sec,omitempty"`
}
type quicFabricSession struct {
conn *quic.Conn
stream *quic.Stream
inbound chan fabricproto.Frame
errors chan error
done chan struct{}
closeOnce sync.Once
writeMu sync.Mutex
maxPayload int
timeout time.Duration
closeConn bool
transport *QUICFabricTransport
connKey string
}
type quicFabricConnEntry struct {
conn *quic.Conn
lastUsed time.Time
activeStreams int
}
func NewQUICFabricTransport(config *quic.Config) *QUICFabricTransport {
return &QUICFabricTransport{Config: config, IdleTTL: defaultQUICFabricConnIdleTTL, MaxStreamsPerConn: defaultQUICFabricMaxStreamsPerConn, conns: map[string]*quicFabricConnEntry{}, reverseConns: map[string]*quicFabricConnEntry{}}
}
func (t *QUICFabricTransport) SetInboundHandlers(production func(context.Context, ProductionEnvelope) (ProductionForwardResult, error), synthetic func(context.Context, SyntheticEnvelope) (SyntheticEnvelope, error), logger FabricSessionEventLogger) {
t.SetInboundHandlersWithWebIngress(production, nil, synthetic, logger)
}
func (t *QUICFabricTransport) SetInboundHandlersWithWebIngress(production func(context.Context, ProductionEnvelope) (ProductionForwardResult, error), webIngress func(context.Context, []byte) ([]byte, error), synthetic func(context.Context, SyntheticEnvelope) (SyntheticEnvelope, error), logger FabricSessionEventLogger) {
if t == nil {
return
}
t.mu.Lock()
t.inboundProductionHandler = production
t.inboundWebIngressHandler = webIngress
t.inboundSyntheticHandler = synthetic
t.logger = logger
t.mu.Unlock()
}
func (t *QUICFabricTransport) SetInboundFabricControlHandler(handler func(context.Context, []byte) ([]byte, error)) {
if t == nil {
return
}
t.mu.Lock()
t.inboundFabricControlHandler = handler
t.mu.Unlock()
}
func (t *QUICFabricTransport) SetLocalPeerID(peerID string) {
if t == nil {
return
}
t.mu.Lock()
t.LocalPeerID = strings.TrimSpace(peerID)
t.mu.Unlock()
}
func quicTLSConfigForTarget(target FabricTransportTarget) *tls.Config {
expectedFingerprint := normalizeCertSHA256(target.PeerCertSHA256)
config := &tls.Config{NextProtos: []string{fabricQUICNextProto}}
if expectedFingerprint == "" {
return config
}
config.InsecureSkipVerify = true
config.VerifyPeerCertificate = func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
if len(rawCerts) == 0 {
return fmt.Errorf("quic peer certificate missing")
}
sum := sha256.Sum256(rawCerts[0])
actual := hex.EncodeToString(sum[:])
if actual != expectedFingerprint {
return fmt.Errorf("quic peer certificate fingerprint mismatch")
}
return nil
}
return config
}
func normalizeCertSHA256(value string) string {
value = strings.ToLower(strings.TrimSpace(value))
value = strings.ReplaceAll(value, "sha256:", "")
value = strings.ReplaceAll(value, ":", "")
return value
}
func (t *QUICFabricTransport) Connect(ctx context.Context, target FabricTransportTarget) (FabricTransportSession, error) {
if target.Endpoint == "" {
return nil, fmt.Errorf("quic fabric endpoint is required")
}
target.Endpoint = strings.TrimPrefix(strings.TrimSpace(target.Endpoint), "quic://")
tlsConfig := target.TLSConfig
if tlsConfig == nil {
tlsConfig = quicTLSConfigForTarget(target)
} else {
tlsConfig = tlsConfig.Clone()
if len(tlsConfig.NextProtos) == 0 {
tlsConfig.NextProtos = []string{fabricQUICNextProto}
}
}
conn, connKey, closeConn, err := t.connectConn(ctx, target, tlsConfig)
if err != nil {
return nil, err
}
if err := t.reserveStream(connKey, conn); err != nil {
return nil, err
}
stream, err := conn.OpenStreamSync(ctx)
if err != nil {
t.releaseStream(connKey)
t.evictConn(target, conn)
if closeConn {
_ = conn.CloseWithError(1, "open stream failed")
}
return nil, err
}
maxPayload := target.MaxPayload
if maxPayload <= 0 {
maxPayload = fabricproto.DefaultMaxPayload
}
inboundBuffer := target.InboundBuffer
if inboundBuffer <= 0 {
inboundBuffer = 64
}
errorBuffer := target.ErrorBuffer
if errorBuffer <= 0 {
errorBuffer = 8
}
session := &quicFabricSession{
conn: conn,
stream: stream,
inbound: make(chan fabricproto.Frame, inboundBuffer),
errors: make(chan error, errorBuffer),
done: make(chan struct{}),
maxPayload: maxPayload,
timeout: target.Timeout,
closeConn: closeConn,
transport: t,
connKey: connKey,
}
go session.readLoop(context.Background())
return session, nil
}
func (t *QUICFabricTransport) connectConn(ctx context.Context, target FabricTransportTarget, tlsConfig *tls.Config) (*quic.Conn, string, bool, error) {
if t == nil {
conn, err := quic.DialAddr(ctx, target.Endpoint, tlsConfig, nil)
return conn, "", true, err
}
if conn, key, ok := t.reverseConnForTarget(target); ok {
return conn, key, false, nil
}
key := quicFabricConnKey(target)
if key == "" {
conn, err := t.dialAddr(ctx, target.Endpoint, tlsConfig)
return conn, "", true, err
}
t.mu.Lock()
t.pruneIdleLocked(time.Now())
if entry := t.conns[key]; entry != nil && entry.conn != nil {
select {
case <-entry.conn.Context().Done():
delete(t.conns, key)
t.stats.ClosedEvicted++
default:
entry.lastUsed = time.Now()
t.stats.Reuses++
t.mu.Unlock()
return entry.conn, key, false, nil
}
}
t.mu.Unlock()
conn, err := t.dialAddr(ctx, target.Endpoint, tlsConfig)
if err != nil {
t.mu.Lock()
t.stats.OpenFailures++
t.mu.Unlock()
return nil, "", false, err
}
t.mu.Lock()
t.pruneIdleLocked(time.Now())
if existing := t.conns[key]; existing != nil && existing.conn != nil {
select {
case <-existing.conn.Context().Done():
delete(t.conns, key)
t.stats.ClosedEvicted++
default:
existing.lastUsed = time.Now()
t.stats.Reuses++
t.mu.Unlock()
_ = conn.CloseWithError(0, "duplicate connection")
return existing.conn, key, false, nil
}
}
if t.conns == nil {
t.conns = map[string]*quicFabricConnEntry{}
}
t.conns[key] = &quicFabricConnEntry{conn: conn, lastUsed: time.Now()}
t.stats.Opens++
t.mu.Unlock()
go t.acceptInboundStreams(context.Background(), conn)
go t.sendReverseHello(context.Background(), conn)
return conn, key, false, nil
}
func (t *QUICFabricTransport) dialAddr(ctx context.Context, endpoint string, tlsConfig *tls.Config) (*quic.Conn, error) {
if t != nil && t.DialAddr != nil {
return t.DialAddr(ctx, endpoint, tlsConfig, t.Config)
}
return quic.DialAddr(ctx, endpoint, tlsConfig, t.Config)
}
func DialQUICAddrWithPacketConn(ctx context.Context, endpoint string, packetConn net.PacketConn, tlsConfig *tls.Config, config *quic.Config) (*quic.Conn, error) {
if packetConn == nil {
return nil, fmt.Errorf("quic packet connection is required")
}
addr, err := net.ResolveUDPAddr("udp", strings.TrimPrefix(strings.TrimSpace(endpoint), "quic://"))
if err != nil {
_ = packetConn.Close()
return nil, err
}
transport := &quic.Transport{Conn: packetConn}
conn, err := transport.Dial(ctx, addr, tlsConfig, config)
if err != nil {
_ = transport.Close()
return nil, err
}
go func() {
<-conn.Context().Done()
_ = transport.Close()
}()
return conn, nil
}
func (t *QUICFabricTransport) sendReverseHello(ctx context.Context, conn *quic.Conn) {
if t == nil || conn == nil {
return
}
localPeerID := t.localPeerID()
if localPeerID == "" {
t.mu.Lock()
t.stats.ReverseHelloFailed++
t.mu.Unlock()
return
}
helloCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
stream, err := conn.OpenStreamSync(helloCtx)
if err != nil {
t.mu.Lock()
t.stats.ReverseHelloFailed++
t.mu.Unlock()
return
}
defer func() { _ = stream.Close() }()
if err := fabricproto.WriteFrame(stream, fabricproto.Frame{
Type: fabricproto.FramePing,
Sequence: 1,
Payload: []byte(fabricQUICReverseHelloPrefix + localPeerID),
}); err != nil {
t.mu.Lock()
t.stats.ReverseHelloFailed++
t.mu.Unlock()
return
}
t.mu.Lock()
t.stats.ReverseHelloSent++
t.mu.Unlock()
_, _ = fabricproto.ReadFrame(stream, fabricproto.DefaultMaxPayload)
}
func (t *QUICFabricTransport) acceptInboundStreams(ctx context.Context, conn *quic.Conn) {
if t == nil || conn == nil {
return
}
for {
stream, err := conn.AcceptStream(ctx)
if err != nil {
return
}
go t.handleInboundStream(ctx, conn, stream)
}
}
func (t *QUICFabricTransport) handleInboundStream(ctx context.Context, conn *quic.Conn, stream *quic.Stream) {
session := fabricproto.NewSession(fabricproto.SessionConfig{})
defer func() { _ = stream.Close() }()
t.logFabricSession(FabricSessionEventLogEntry{
Event: "fabric_session_quic_reverse_stream_opened",
AcceptedBy: "quic_reverse",
RemoteAddr: conn.RemoteAddr().String(),
})
defer t.logFabricSession(FabricSessionEventLogEntry{
Event: "fabric_session_quic_reverse_stream_closed",
AcceptedBy: "quic_reverse",
RemoteAddr: conn.RemoteAddr().String(),
})
for {
select {
case <-ctx.Done():
_ = stream.Close()
return
default:
}
frame, err := fabricproto.ReadFrame(stream, fabricproto.DefaultMaxPayload)
if err != nil {
return
}
t.registerReverseHelloFrame(conn, frame)
if t.handleInboundProductionForwardFrame(ctx, stream, frame) {
continue
}
if t.handleInboundWebIngressForwardFrame(ctx, stream, frame) {
continue
}
if t.handleInboundFabricControlForwardFrame(ctx, stream, frame) {
continue
}
if t.handleInboundSyntheticForwardFrame(ctx, stream, frame) {
continue
}
event, responses, err := session.HandleFrame(frame)
if err != nil {
_ = stream.Close()
return
}
if event.Type != fabricproto.SessionEventNone {
t.logFabricSession(FabricSessionEventLogEntry{
Event: "fabric_session_reverse_event",
SessionEvent: event.Type,
StreamID: event.StreamID,
Sequence: event.Sequence,
TrafficClass: event.TrafficClass,
AcceptedBy: "quic_reverse",
RemoteAddr: conn.RemoteAddr().String(),
})
}
for _, response := range responses {
if err := fabricproto.WriteFrame(stream, response); err != nil {
return
}
}
}
}
func (t *QUICFabricTransport) registerReverseHelloFrame(conn *quic.Conn, frame fabricproto.Frame) {
if t == nil || conn == nil || frame.Type != fabricproto.FramePing {
return
}
payload := string(frame.Payload)
if !strings.HasPrefix(payload, fabricQUICReverseHelloPrefix) {
return
}
peerID := strings.TrimPrefix(payload, fabricQUICReverseHelloPrefix)
t.RegisterReverseConn(peerID, conn)
t.logFabricSession(FabricSessionEventLogEntry{
Event: "fabric_session_quic_reverse_registered",
AcceptedBy: "quic_reverse_hello",
RemoteAddr: conn.RemoteAddr().String(),
PeerID: peerID,
})
}
func (t *QUICFabricTransport) handleInboundProductionForwardFrame(ctx context.Context, stream *quic.Stream, frame fabricproto.Frame) bool {
if frame.Type != fabricproto.FrameData || frame.StreamID != ProductionForwardQUICStreamID {
return false
}
response := quicProductionForwardResponse{}
productionHandler, _, _, _, _ := t.inboundHandlers()
if productionHandler == nil {
response.Error = ErrForwardRuntimeUnavailable.Error()
} else {
var envelope ProductionEnvelope
if err := json.Unmarshal(frame.Payload, &envelope); err != nil {
response.Error = "invalid production mesh envelope"
} else if result, err := productionHandler(ctx, envelope); err != nil {
response.Error = err.Error()
} else {
response.Result = result
}
}
payload, err := json.Marshal(response)
if err == nil {
_ = fabricproto.WriteFrame(stream, fabricproto.Frame{Type: fabricproto.FrameData, TrafficClass: fabricproto.TrafficClassReliable, StreamID: ProductionForwardQUICStreamID, Sequence: frame.Sequence, Payload: payload})
}
return true
}
func (t *QUICFabricTransport) handleInboundWebIngressForwardFrame(ctx context.Context, stream *quic.Stream, frame fabricproto.Frame) bool {
if frame.Type != fabricproto.FrameData || frame.StreamID != WebIngressForwardQUICStreamID {
return false
}
response := quicWebIngressForwardResponse{}
_, webIngressHandler, _, _, _ := t.inboundHandlers()
if webIngressHandler == nil {
response.Error = ErrForwardRuntimeUnavailable.Error()
} else if payload, err := webIngressHandler(ctx, append([]byte(nil), frame.Payload...)); err != nil {
response.Error = err.Error()
} else {
response.Payload = append(json.RawMessage(nil), payload...)
}
payload, err := json.Marshal(response)
if err == nil {
_ = fabricproto.WriteFrame(stream, fabricproto.Frame{Type: fabricproto.FrameData, TrafficClass: fabricproto.TrafficClassReliable, StreamID: WebIngressForwardQUICStreamID, Sequence: frame.Sequence, Payload: payload})
}
return true
}
func (t *QUICFabricTransport) handleInboundFabricControlForwardFrame(ctx context.Context, stream *quic.Stream, frame fabricproto.Frame) bool {
if frame.Type != fabricproto.FrameData || frame.StreamID != FabricControlForwardQUICStreamID {
return false
}
response := quicFabricControlForwardResponse{}
_, _, fabricControlHandler, _, _ := t.inboundHandlers()
if fabricControlHandler == nil {
response.Error = ErrForwardRuntimeUnavailable.Error()
} else if payload, err := fabricControlHandler(ctx, append([]byte(nil), frame.Payload...)); err != nil {
response.Error = err.Error()
} else {
response.Payload = append(json.RawMessage(nil), payload...)
}
payload, err := json.Marshal(response)
if err == nil {
_ = fabricproto.WriteFrame(stream, fabricproto.Frame{Type: fabricproto.FrameData, TrafficClass: fabricproto.TrafficClassReliable, StreamID: FabricControlForwardQUICStreamID, Sequence: frame.Sequence, Payload: payload})
}
return true
}
func (t *QUICFabricTransport) handleInboundSyntheticForwardFrame(ctx context.Context, stream *quic.Stream, frame fabricproto.Frame) bool {
if frame.Type != fabricproto.FrameData || frame.StreamID != SyntheticForwardQUICStreamID {
return false
}
response := quicSyntheticForwardResponse{}
_, _, _, syntheticHandler, _ := t.inboundHandlers()
if syntheticHandler == nil {
response.Error = ErrMeshRuntimeDisabled.Error()
} else {
var envelope SyntheticEnvelope
if err := json.Unmarshal(frame.Payload, &envelope); err != nil {
response.Error = "invalid synthetic mesh envelope"
} else if ack, err := syntheticHandler(ctx, envelope); err != nil {
response.Error = err.Error()
} else {
response.Envelope = ack
}
}
payload, err := json.Marshal(response)
if err == nil {
_ = fabricproto.WriteFrame(stream, fabricproto.Frame{Type: fabricproto.FrameData, TrafficClass: fabricproto.TrafficClassReliable, StreamID: SyntheticForwardQUICStreamID, Sequence: frame.Sequence, Payload: payload})
}
return true
}
func (t *QUICFabricTransport) inboundHandlers() (func(context.Context, ProductionEnvelope) (ProductionForwardResult, error), func(context.Context, []byte) ([]byte, error), func(context.Context, []byte) ([]byte, error), func(context.Context, SyntheticEnvelope) (SyntheticEnvelope, error), FabricSessionEventLogger) {
if t == nil {
return nil, nil, nil, nil, nil
}
t.mu.Lock()
defer t.mu.Unlock()
return t.inboundProductionHandler, t.inboundWebIngressHandler, t.inboundFabricControlHandler, t.inboundSyntheticHandler, t.logger
}
func (t *QUICFabricTransport) localPeerID() string {
if t == nil {
return ""
}
t.mu.Lock()
defer t.mu.Unlock()
return strings.TrimSpace(t.LocalPeerID)
}
func (t *QUICFabricTransport) logFabricSession(entry FabricSessionEventLogEntry) {
_, _, _, _, logger := t.inboundHandlers()
if logger != nil {
logger(entry)
}
}
func (t *QUICFabricTransport) RegisterReverseConn(peerID string, conn *quic.Conn) {
if t == nil || conn == nil {
return
}
peerID = strings.TrimSpace(peerID)
if peerID == "" {
return
}
t.mu.Lock()
defer t.mu.Unlock()
if t.reverseConns == nil {
t.reverseConns = map[string]*quicFabricConnEntry{}
}
if existing := t.reverseConns[peerID]; existing != nil && existing.conn != nil && existing.conn != conn {
select {
case <-existing.conn.Context().Done():
default:
_ = existing.conn.CloseWithError(0, "reverse connection replaced")
}
}
t.reverseConns[peerID] = &quicFabricConnEntry{conn: conn, lastUsed: time.Now()}
t.stats.ReverseRegisters++
}
func (t *QUICFabricTransport) reverseConnForTarget(target FabricTransportTarget) (*quic.Conn, string, bool) {
peerID := strings.TrimSpace(target.PeerID)
if t == nil || peerID == "" || !fabricTransportPrefersReverseConn(target.Transport) {
return nil, "", false
}
t.mu.Lock()
defer t.mu.Unlock()
t.pruneIdleLocked(time.Now())
entry := t.reverseConns[peerID]
if entry == nil || entry.conn == nil {
return nil, "", false
}
select {
case <-entry.conn.Context().Done():
delete(t.reverseConns, peerID)
t.stats.ClosedEvicted++
return nil, "", false
default:
entry.lastUsed = time.Now()
t.stats.ReverseReuses++
return entry.conn, quicFabricReverseConnKey(peerID), true
}
}
func (t *QUICFabricTransport) reserveStream(key string, conn *quic.Conn) error {
if t == nil || key == "" {
return nil
}
t.mu.Lock()
defer t.mu.Unlock()
entry := t.connEntryLocked(key)
if entry == nil || entry.conn != conn {
return fmt.Errorf("quic fabric connection is not cached")
}
limit := t.MaxStreamsPerConn
if limit <= 0 {
limit = defaultQUICFabricMaxStreamsPerConn
}
if entry.activeStreams >= limit {
t.stats.StreamLimitRejects++
return ErrQUICFabricStreamLimitReached
}
entry.activeStreams++
entry.lastUsed = time.Now()
t.stats.StreamOpens++
return nil
}
func (t *QUICFabricTransport) releaseStream(key string) {
if t == nil || key == "" {
return
}
t.mu.Lock()
if entry := t.connEntryLocked(key); entry != nil {
if entry.activeStreams > 0 {
entry.activeStreams--
}
entry.lastUsed = time.Now()
}
t.stats.StreamCloses++
t.mu.Unlock()
}
func (t *QUICFabricTransport) connEntryLocked(key string) *quicFabricConnEntry {
if t == nil || key == "" {
return nil
}
if strings.HasPrefix(key, "reverse\x00") {
return t.reverseConns[strings.TrimPrefix(key, "reverse\x00")]
}
return t.conns[key]
}
func (t *QUICFabricTransport) evictConn(target FabricTransportTarget, conn *quic.Conn) {
if t == nil || conn == nil {
return
}
key := quicFabricConnKey(target)
if key == "" {
return
}
t.mu.Lock()
if entry := t.conns[key]; entry != nil && entry.conn == conn {
delete(t.conns, key)
t.stats.ClosedEvicted++
}
t.mu.Unlock()
}
func (t *QUICFabricTransport) pruneIdleLocked(now time.Time) {
if t == nil || len(t.conns) == 0 {
return
}
ttl := t.IdleTTL
if ttl <= 0 {
ttl = defaultQUICFabricConnIdleTTL
}
for key, entry := range t.conns {
if entry == nil || entry.conn == nil {
delete(t.conns, key)
continue
}
if !entry.lastUsed.IsZero() && now.Sub(entry.lastUsed) > ttl {
if entry.activeStreams > 0 {
continue
}
_ = entry.conn.CloseWithError(0, "idle")
delete(t.conns, key)
t.stats.IdleEvicted++
}
}
for peerID, entry := range t.reverseConns {
if entry == nil || entry.conn == nil {
delete(t.reverseConns, peerID)
continue
}
if !entry.lastUsed.IsZero() && now.Sub(entry.lastUsed) > ttl {
if entry.activeStreams > 0 {
continue
}
_ = entry.conn.CloseWithError(0, "idle reverse")
delete(t.reverseConns, peerID)
t.stats.IdleEvicted++
}
}
}
func quicFabricConnKey(target FabricTransportTarget) string {
peerID := strings.TrimSpace(target.PeerID)
endpoint := strings.TrimPrefix(strings.TrimSpace(target.Endpoint), "quic://")
if peerID == "" || endpoint == "" {
return ""
}
return peerID + "\x00" + endpoint + "\x00" + normalizeCertSHA256(target.PeerCertSHA256)
}
func parseQUICFabricConnKey(key string) (peerID string, endpoint string, certSHA256 string) {
parts := strings.SplitN(key, "\x00", 3)
if len(parts) > 0 {
peerID = parts[0]
}
if len(parts) > 1 {
endpoint = parts[1]
}
if len(parts) > 2 {
certSHA256 = parts[2]
}
return peerID, endpoint, certSHA256
}
func quicFabricReverseConnKey(peerID string) string {
peerID = strings.TrimSpace(peerID)
if peerID == "" {
return ""
}
return "reverse\x00" + peerID
}
func fabricTransportPrefersReverseConn(transport string) bool {
switch strings.ToLower(strings.TrimSpace(transport)) {
case "reverse_quic", "relay_quic":
return true
default:
return false
}
}
func (t *QUICFabricTransport) Close() error {
if t == nil {
return nil
}
t.mu.Lock()
t.stats.CloseAllCalls++
conns := t.conns
t.conns = map[string]*quicFabricConnEntry{}
reverseConns := t.reverseConns
t.reverseConns = map[string]*quicFabricConnEntry{}
t.mu.Unlock()
for _, entry := range conns {
if entry != nil && entry.conn != nil {
_ = entry.conn.CloseWithError(0, "closed")
}
}
for _, entry := range reverseConns {
if entry != nil && entry.conn != nil {
_ = entry.conn.CloseWithError(0, "closed")
}
}
return nil
}
func (t *QUICFabricTransport) Snapshot() QUICFabricTransportSnapshot {
if t == nil {
return QUICFabricTransportSnapshot{SchemaVersion: "rap.quic_fabric_transport.v1"}
}
t.mu.Lock()
defer t.mu.Unlock()
t.pruneIdleLocked(time.Now())
limit := t.MaxStreamsPerConn
if limit <= 0 {
limit = defaultQUICFabricMaxStreamsPerConn
}
snapshot := QUICFabricTransportSnapshot{
SchemaVersion: "rap.quic_fabric_transport.v1",
LocalPeerID: strings.TrimSpace(t.LocalPeerID),
MaxStreamsPerConn: limit,
Stats: t.stats,
}
for key, entry := range t.conns {
if entry == nil || entry.conn == nil {
delete(t.conns, key)
continue
}
select {
case <-entry.conn.Context().Done():
delete(t.conns, key)
t.stats.ClosedEvicted++
snapshot.Stats.ClosedEvicted++
default:
snapshot.ActiveCount++
snapshot.ActiveStreams += entry.activeStreams
peerID, endpoint, certSHA256 := parseQUICFabricConnKey(key)
connSnapshot := QUICFabricConnSnapshot{
PeerID: peerID,
Endpoint: endpoint,
CertSHA256: certSHA256,
Direction: "outbound",
ActiveStreams: entry.activeStreams,
MaxStreams: limit,
Saturated: entry.activeStreams >= limit,
}
if !entry.lastUsed.IsZero() {
connSnapshot.LastUsedUnixSec = entry.lastUsed.UTC().Unix()
}
if limit > 0 {
connSnapshot.CapacityPressurePercent = (entry.activeStreams * 100) / limit
}
snapshot.Connections = append(snapshot.Connections, connSnapshot)
if entry.activeStreams >= limit {
snapshot.SaturatedConnections++
}
}
}
for peerID, entry := range t.reverseConns {
if entry == nil || entry.conn == nil {
delete(t.reverseConns, peerID)
continue
}
select {
case <-entry.conn.Context().Done():
delete(t.reverseConns, peerID)
t.stats.ClosedEvicted++
snapshot.Stats.ClosedEvicted++
default:
snapshot.ActiveCount++
snapshot.ActiveStreams += entry.activeStreams
connSnapshot := QUICFabricConnSnapshot{
PeerID: peerID,
Endpoint: entry.conn.RemoteAddr().String(),
Direction: "reverse",
ActiveStreams: entry.activeStreams,
MaxStreams: limit,
Saturated: entry.activeStreams >= limit,
}
if !entry.lastUsed.IsZero() {
connSnapshot.LastUsedUnixSec = entry.lastUsed.UTC().Unix()
}
if limit > 0 {
connSnapshot.CapacityPressurePercent = (entry.activeStreams * 100) / limit
}
snapshot.Connections = append(snapshot.Connections, connSnapshot)
if entry.activeStreams >= limit {
snapshot.SaturatedConnections++
}
}
}
if snapshot.ActiveCount > 0 && limit > 0 {
capacity := snapshot.ActiveCount * limit
if capacity > 0 {
snapshot.CapacityPressurePercent = (snapshot.ActiveStreams * 100) / capacity
}
}
sort.Slice(snapshot.Connections, func(i, j int) bool {
if snapshot.Connections[i].PeerID != snapshot.Connections[j].PeerID {
return snapshot.Connections[i].PeerID < snapshot.Connections[j].PeerID
}
if snapshot.Connections[i].Endpoint != snapshot.Connections[j].Endpoint {
return snapshot.Connections[i].Endpoint < snapshot.Connections[j].Endpoint
}
return snapshot.Connections[i].CertSHA256 < snapshot.Connections[j].CertSHA256
})
return snapshot
}
func (s *quicFabricSession) Send(ctx context.Context, frame fabricproto.Frame) error {
if s == nil || s.stream == nil {
return fmt.Errorf("quic fabric session is closed")
}
select {
case <-s.done:
return fmt.Errorf("quic fabric session is closed")
default:
}
s.writeMu.Lock()
defer s.writeMu.Unlock()
s.applyWriteDeadline(ctx)
return fabricproto.WriteFrame(s.stream, frame)
}
func (s *quicFabricSession) Frames() <-chan fabricproto.Frame {
if s == nil {
return nil
}
return s.inbound
}
func (s *quicFabricSession) Errors() <-chan error {
if s == nil {
return nil
}
return s.errors
}
func (s *quicFabricSession) Close() error {
if s == nil {
return nil
}
var err error
s.closeOnce.Do(func() {
close(s.done)
if s.stream != nil {
s.stream.CancelRead(0)
err = s.stream.Close()
}
if s.transport != nil {
s.transport.releaseStream(s.connKey)
}
if s.conn != nil {
if s.closeConn {
_ = s.conn.CloseWithError(0, "closed")
}
}
})
return err
}
func (s *quicFabricSession) Closed() bool {
if s == nil {
return true
}
select {
case <-s.done:
return true
default:
return false
}
}
func (s *quicFabricSession) readLoop(ctx context.Context) {
defer s.Close()
for {
s.applyReadDeadline(ctx)
frame, err := fabricproto.ReadFrame(s.stream, s.maxPayload)
if err != nil {
s.reportError(err)
return
}
select {
case <-ctx.Done():
s.reportError(ctx.Err())
return
case <-s.done:
return
case s.inbound <- frame:
}
}
}
func (s *quicFabricSession) reportError(err error) {
if err == nil {
return
}
select {
case s.errors <- err:
default:
}
}
func (s *quicFabricSession) applyReadDeadline(ctx context.Context) {
if deadline, ok := ctx.Deadline(); ok {
_ = s.stream.SetReadDeadline(deadline)
} else if s.timeout > 0 {
_ = s.stream.SetReadDeadline(time.Now().Add(s.timeout))
}
}
func (s *quicFabricSession) applyWriteDeadline(ctx context.Context) {
if deadline, ok := ctx.Deadline(); ok {
_ = s.stream.SetWriteDeadline(deadline)
} else if s.timeout > 0 {
_ = s.stream.SetWriteDeadline(time.Now().Add(s.timeout))
}
}