190 lines
5.6 KiB
Go
190 lines
5.6 KiB
Go
package vpnruntime
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/example/remote-access-platform/agents/rap-node-agent/internal/fabricproto"
|
|
"github.com/example/remote-access-platform/agents/rap-node-agent/internal/mesh"
|
|
)
|
|
|
|
type FabricSessionFrameWriter interface {
|
|
SendFrame(context.Context, fabricproto.Frame) error
|
|
}
|
|
|
|
type FabricSessionPacketPeerRegistry struct {
|
|
mu sync.RWMutex
|
|
peers map[string]FabricSessionPacketPeer
|
|
}
|
|
|
|
type FabricSessionPacketPeer struct {
|
|
VPNConnectionID string
|
|
Sender FabricSessionFrameWriter
|
|
StreamID uint64
|
|
StreamIDsByTrafficClass map[string][]uint64
|
|
RegisteredAt time.Time
|
|
LastPacketAt time.Time
|
|
}
|
|
|
|
type FabricSessionPacketPeerTransport struct {
|
|
Registry *FabricSessionPacketPeerRegistry
|
|
Inbox *FabricPacketInbox
|
|
VPNConnectionID string
|
|
}
|
|
|
|
func NewFabricSessionPacketPeerRegistry() *FabricSessionPacketPeerRegistry {
|
|
return &FabricSessionPacketPeerRegistry{peers: map[string]FabricSessionPacketPeer{}}
|
|
}
|
|
|
|
func (r *FabricSessionPacketPeerRegistry) RegisterFrame(ctx context.Context, sender FabricSessionFrameWriter, frame fabricproto.Frame) (bool, error) {
|
|
if r == nil || sender == nil || frame.Type != fabricproto.FrameData || frame.StreamID == 0 {
|
|
return false, nil
|
|
}
|
|
payload, err := DecodeFabricVPNPacketDataFrame(frame)
|
|
if err != nil {
|
|
return false, nil
|
|
}
|
|
if payload.VPNConnectionID == "" {
|
|
return false, nil
|
|
}
|
|
now := time.Now().UTC()
|
|
r.mu.Lock()
|
|
if r.peers == nil {
|
|
r.peers = map[string]FabricSessionPacketPeer{}
|
|
}
|
|
peer := r.peers[payload.VPNConnectionID]
|
|
if peer.RegisteredAt.IsZero() {
|
|
peer.RegisteredAt = now
|
|
}
|
|
peer.VPNConnectionID = payload.VPNConnectionID
|
|
peer.Sender = sender
|
|
peer.StreamID = frame.StreamID
|
|
peer.LastPacketAt = now
|
|
if peer.StreamIDsByTrafficClass == nil {
|
|
peer.StreamIDsByTrafficClass = map[string][]uint64{}
|
|
}
|
|
trafficClass := fabricSessionTrafficClassName(frame.TrafficClass)
|
|
if trafficClass != "" && !containsUint64(peer.StreamIDsByTrafficClass[trafficClass], frame.StreamID) {
|
|
peer.StreamIDsByTrafficClass[trafficClass] = append(peer.StreamIDsByTrafficClass[trafficClass], frame.StreamID)
|
|
}
|
|
r.peers[payload.VPNConnectionID] = peer
|
|
r.mu.Unlock()
|
|
return true, nil
|
|
}
|
|
|
|
func (r *FabricSessionPacketPeerRegistry) TransportFor(vpnConnectionID string, inbox *FabricPacketInbox) PacketTransport {
|
|
if r == nil || inbox == nil || vpnConnectionID == "" {
|
|
return nil
|
|
}
|
|
r.mu.RLock()
|
|
peer, ok := r.peers[vpnConnectionID]
|
|
r.mu.RUnlock()
|
|
if !ok || peer.Sender == nil || peer.StreamID == 0 {
|
|
return nil
|
|
}
|
|
return &FabricSessionPacketTransport{
|
|
Sender: fabricSessionFrameWriterAdapter{writer: peer.Sender},
|
|
Inbox: inbox,
|
|
StreamID: peer.StreamID,
|
|
StreamIDsByTrafficClass: copyStreamIDsByClass(peer.StreamIDsByTrafficClass),
|
|
VPNConnectionID: vpnConnectionID,
|
|
SendDirection: FabricDirectionGatewayToClient,
|
|
ReceiveDirection: FabricDirectionClientToGateway,
|
|
}
|
|
}
|
|
|
|
func (t *FabricSessionPacketPeerTransport) SendGatewayPacketBatch(ctx context.Context, packets [][]byte) error {
|
|
if t == nil || t.Registry == nil || t.Inbox == nil || t.VPNConnectionID == "" {
|
|
return mesh.ErrForwardRuntimeUnavailable
|
|
}
|
|
transport := t.Registry.TransportFor(t.VPNConnectionID, t.Inbox)
|
|
if transport == nil {
|
|
return mesh.ErrForwardRuntimeUnavailable
|
|
}
|
|
return transport.SendGatewayPacketBatch(ctx, packets)
|
|
}
|
|
|
|
func (t *FabricSessionPacketPeerTransport) ReceiveGatewayPacketBatch(ctx context.Context, timeout time.Duration) ([][]byte, error) {
|
|
if t == nil || t.Inbox == nil || t.VPNConnectionID == "" {
|
|
return nil, mesh.ErrForwardRuntimeUnavailable
|
|
}
|
|
return t.Inbox.Receive(ctx, t.VPNConnectionID, FabricDirectionClientToGateway, timeout)
|
|
}
|
|
|
|
func (t *FabricSessionPacketPeerTransport) Snapshot() map[string]any {
|
|
if t == nil {
|
|
return map[string]any{
|
|
"transport": "fabric_session_peer_dynamic",
|
|
"peer_ready": false,
|
|
}
|
|
}
|
|
ready := 0
|
|
if t.Registry != nil {
|
|
if transport := t.Registry.TransportFor(t.VPNConnectionID, t.Inbox); transport != nil {
|
|
ready = 1
|
|
}
|
|
}
|
|
return map[string]any{
|
|
"transport": "fabric_session_peer_dynamic",
|
|
"vpn_connection_id": t.VPNConnectionID,
|
|
"peer_ready": ready == 1,
|
|
}
|
|
}
|
|
|
|
func (r *FabricSessionPacketPeerRegistry) Snapshot() map[string]any {
|
|
if r == nil {
|
|
return map[string]any{"ready": 0}
|
|
}
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
out := map[string]any{"ready": len(r.peers)}
|
|
items := make([]map[string]any, 0, len(r.peers))
|
|
for _, peer := range r.peers {
|
|
item := map[string]any{
|
|
"vpn_connection_id": peer.VPNConnectionID,
|
|
"stream_id": peer.StreamID,
|
|
}
|
|
if !peer.RegisteredAt.IsZero() {
|
|
item["registered_at"] = peer.RegisteredAt.Format(time.RFC3339Nano)
|
|
}
|
|
if !peer.LastPacketAt.IsZero() {
|
|
item["last_packet_at"] = peer.LastPacketAt.Format(time.RFC3339Nano)
|
|
}
|
|
items = append(items, item)
|
|
}
|
|
out["peers"] = items
|
|
return out
|
|
}
|
|
|
|
type fabricSessionFrameWriterAdapter struct {
|
|
writer FabricSessionFrameWriter
|
|
}
|
|
|
|
func (a fabricSessionFrameWriterAdapter) Send(ctx context.Context, frame fabricproto.Frame) error {
|
|
if a.writer == nil {
|
|
return mesh.ErrForwardRuntimeUnavailable
|
|
}
|
|
return a.writer.SendFrame(ctx, frame)
|
|
}
|
|
|
|
func containsUint64(values []uint64, value uint64) bool {
|
|
for _, item := range values {
|
|
if item == value {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func copyStreamIDsByClass(values map[string][]uint64) map[string][]uint64 {
|
|
if len(values) == 0 {
|
|
return nil
|
|
}
|
|
out := make(map[string][]uint64, len(values))
|
|
for key, ids := range values {
|
|
out[key] = append([]uint64(nil), ids...)
|
|
}
|
|
return out
|
|
}
|