2024-10-31 21:05:15 +00:00
|
|
|
package v3
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"net"
|
|
|
|
"net/netip"
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
"github.com/rs/zerolog"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2024-11-06 20:06:07 +00:00
|
|
|
// ErrSessionNotFound indicates that a session has not been registered yet for the request id.
|
2024-11-07 19:02:55 +00:00
|
|
|
ErrSessionNotFound = errors.New("flow not found")
|
2024-11-06 20:06:07 +00:00
|
|
|
// ErrSessionBoundToOtherConn is returned when a registration already exists for a different connection.
|
2024-11-07 19:02:55 +00:00
|
|
|
ErrSessionBoundToOtherConn = errors.New("flow is in use by another connection")
|
2024-11-06 20:06:07 +00:00
|
|
|
// ErrSessionAlreadyRegistered is returned when a registration already exists for this connection.
|
2024-11-07 19:02:55 +00:00
|
|
|
ErrSessionAlreadyRegistered = errors.New("flow is already registered for this connection")
|
2024-10-31 21:05:15 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type SessionManager interface {
|
|
|
|
// RegisterSession will register a new session if it does not already exist for the request ID.
|
|
|
|
// During new session creation, the session will also bind the UDP socket for the origin.
|
|
|
|
// If the session exists for a different connection, it will return [ErrSessionBoundToOtherConn].
|
2024-11-06 20:06:07 +00:00
|
|
|
RegisterSession(request *UDPSessionRegistrationDatagram, conn DatagramConn) (Session, error)
|
2024-10-31 21:05:15 +00:00
|
|
|
// GetSession returns an active session if available for the provided connection.
|
|
|
|
// If the session does not exist, it will return [ErrSessionNotFound]. If the session exists for a different
|
|
|
|
// connection, it will return [ErrSessionBoundToOtherConn].
|
|
|
|
GetSession(requestID RequestID) (Session, error)
|
|
|
|
// UnregisterSession will remove a session from the current session manager. It will attempt to close the session
|
|
|
|
// before removal.
|
|
|
|
UnregisterSession(requestID RequestID)
|
|
|
|
}
|
|
|
|
|
|
|
|
type DialUDP func(dest netip.AddrPort) (*net.UDPConn, error)
|
|
|
|
|
|
|
|
type sessionManager struct {
|
2024-11-12 18:54:37 +00:00
|
|
|
sessions map[RequestID]Session
|
|
|
|
mutex sync.RWMutex
|
|
|
|
originDialer DialUDP
|
|
|
|
metrics Metrics
|
|
|
|
log *zerolog.Logger
|
2024-10-31 21:05:15 +00:00
|
|
|
}
|
|
|
|
|
2024-11-07 19:02:55 +00:00
|
|
|
func NewSessionManager(metrics Metrics, log *zerolog.Logger, originDialer DialUDP) SessionManager {
|
2024-10-31 21:05:15 +00:00
|
|
|
return &sessionManager{
|
2024-11-12 18:54:37 +00:00
|
|
|
sessions: make(map[RequestID]Session),
|
|
|
|
originDialer: originDialer,
|
|
|
|
metrics: metrics,
|
|
|
|
log: log,
|
2024-10-31 21:05:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-06 20:06:07 +00:00
|
|
|
func (s *sessionManager) RegisterSession(request *UDPSessionRegistrationDatagram, conn DatagramConn) (Session, error) {
|
2024-10-31 21:05:15 +00:00
|
|
|
s.mutex.Lock()
|
|
|
|
defer s.mutex.Unlock()
|
|
|
|
// Check to make sure session doesn't already exist for requestID
|
2024-11-06 20:06:07 +00:00
|
|
|
if session, exists := s.sessions[request.RequestID]; exists {
|
|
|
|
if conn.ID() == session.ConnectionID() {
|
|
|
|
return nil, ErrSessionAlreadyRegistered
|
|
|
|
}
|
2024-10-31 21:05:15 +00:00
|
|
|
return nil, ErrSessionBoundToOtherConn
|
|
|
|
}
|
|
|
|
// Attempt to bind the UDP socket for the new session
|
2024-11-12 18:54:37 +00:00
|
|
|
origin, err := s.originDialer(request.Dest)
|
2024-10-31 21:05:15 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// Create and insert the new session in the map
|
2024-11-12 18:54:37 +00:00
|
|
|
session := NewSession(
|
|
|
|
request.RequestID,
|
|
|
|
request.IdleDurationHint,
|
|
|
|
origin,
|
|
|
|
origin.RemoteAddr(),
|
|
|
|
origin.LocalAddr(),
|
|
|
|
conn,
|
|
|
|
s.metrics,
|
|
|
|
s.log)
|
2024-10-31 21:05:15 +00:00
|
|
|
s.sessions[request.RequestID] = session
|
|
|
|
return session, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *sessionManager) GetSession(requestID RequestID) (Session, error) {
|
|
|
|
s.mutex.RLock()
|
|
|
|
defer s.mutex.RUnlock()
|
|
|
|
session, exists := s.sessions[requestID]
|
|
|
|
if exists {
|
|
|
|
return session, nil
|
|
|
|
}
|
|
|
|
return nil, ErrSessionNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *sessionManager) UnregisterSession(requestID RequestID) {
|
|
|
|
s.mutex.Lock()
|
|
|
|
defer s.mutex.Unlock()
|
|
|
|
// Get the session and make sure to close it if it isn't already closed
|
|
|
|
session, exists := s.sessions[requestID]
|
|
|
|
if exists {
|
|
|
|
// We ignore any errors when attempting to close the session
|
|
|
|
_ = session.Close()
|
|
|
|
}
|
|
|
|
delete(s.sessions, requestID)
|
|
|
|
}
|